Java 採用 Unicode (UTF-16) 作為內部字元表示,而中國大陸常用 GBK/GB2312,繁體中文環境常用 Big5,早期系統常用 ISO-8859-1。當資料在「編碼 A → 位元組 → 解碼 B」的流程中,編碼與解碼不一致,即產生亂碼。本文將從編碼原理、JVM 處理流程、常見場景、解決方案四個維度,徹底解決 Java 中文編碼難題。
| 編碼 | 描述 | 適用範圍 | 位元組長度 |
|---|---|---|---|
| ASCII | 單位元組,128 字元 | 英文、控制字元 | 1 |
| GB2312 | 國家標準簡體中文 | 中國大陸、新加坡 | 2 (高位元組≥0x80) |
| GBK | GB2312 超集,含繁體、符號 | 中國大陸通用 | 2 |
| Big5 | 繁體中文標準 | 台灣、香港 | 2 |
| UTF-8 | Unicode 變長編碼,ASCII 兼容 | 國際通用、Web 標準 | 1-4 |
| UTF-16 | Java 內部字元編碼 | JVM 內部、Windows API | 2/4 |
| ISO-8859-1 | 單位元組西歐語言,0-255 直接映射 | HTTP 預設、早期系統 | 1 |
資料流向:源碼 → 編譯 → 類別檔 → JVM → 作業系統 → 顯示裝置
javac 預設使用作業系統預設編碼(Windows: GBK, Linux: ISO-8859-1/UTF-8)。javac -encoding UTF-8 MyClass.java(建議專案統一 UTF-8 源碼)。String 內部為 char[] (UTF-16 碼單元)。String.getBytes(charset):按指定編碼編碼為位元組陣列。new String(bytes, charset):按指定編碼解碼位元組陣列。new String(str.getBytes("ISO-8859-1"), "GBK") —— 將錯誤用 ISO-8859-1 解碼的字串,重新按 GBK 解碼還原。
// Windows cmd 預設 GBK,需設定 JVM 參數或程式碼指定
System.out.println(new String("中文".getBytes("UTF-8"), "GBK"));
// 或啟動參數:java -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=GBK Main
// 讀取:明確指定編碼
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream("file.txt"), "GBK"))) { ... }
// 寫入:明確指定編碼
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("file.txt"), "UTF-8"))) { ... }
jdbc:mysql://localhost/db?useUnicode=true&characterEncoding=UTF-8characterEncoding 參數。| 層面 | 配置/代碼 |
|---|---|
| JSP 頁面 | <%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> |
| Servlet 回應 | response.setContentType("text/html;charset=UTF-8"); response.setCharacterEncoding("UTF-8"); |
| 請求解碼 (POST) | request.setCharacterEncoding("UTF-8"); (必須在讀取參數前) |
| 請求解碼 (GET/Tomcat) | server.xml <Connector URIEncoding="UTF-8" /> |
| Spring MVC | 配置 CharacterEncodingFilter (forceEncoding=true) 在 Filter 鏈最前端 |
Content-Type: application/json; charset=UTF-8)。
public final class CharsetUtils {
private CharsetUtils() {}
/** 安全轉碼:bytes 原編碼 → 目標編碼字串 */
public static String convert(byte[] bytes, String fromCharset, String toCharset) {
try {
return new String(bytes, fromCharset);
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Unsupported charset: " + fromCharset, e);
}
}
/** 修正錯誤解碼的字串 (常見: ISO-8859-1 → GBK/UTF-8) */
public static String fixEncoding(String garbled, String wrongCharset, String correctCharset) {
try {
return new String(garbled.getBytes(wrongCharset), correctCharset);
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Charset error", e);
}
}
}
String (UTF-16)。String.getBytes() 無參呼叫:隱含使用平台預設編碼,跨平台極不穩定。永遠用 getBytes(StandardCharsets.UTF_8)。StandardCharsets 常數:StandardCharsets.UTF_8, GBK, ISO_8859_1 避免拼寫錯誤。URIEncoding 與 CharacterEncodingFilter。Java 中文亂碼本質是「編碼不匹配」。理解 Unicode 內部表示、掌握 getBytes/String 建構子雙向轉碼、配置全鏈路 UTF-8、在系統邊界明確處理編碼轉換,是徹底解決問題的鑰匙。現代框架與容器已大幅簡化配置,但核心原理不變,深入理解才能在疑難雜症中游刃有餘。