Java 內部統一使用 UTF-16 (Unicode),而外部世界(文件、數據庫、網絡、操作系統)使用各種編碼(GBK、GB2312、Big5、UTF-8、ISO-8859-1 等)。亂碼 = 字節流按錯誤編碼解碼。本文從編碼常識、全鏈路傳輸、典型場景、工程規範四個維度徹底解決中文問題。
| 編碼 | 特點 | 適用場景 | 字節長度 |
|---|---|---|---|
| ASCII | 1 字節,僅英文 | 早期系統 | 1 |
| GB2312 | 國標簡體,6763 漢字 | 中國大陸舊系統 | 漢字 2 字節 |
| GBK | GB2312 超集,含繁體 | Windows 中文默認 | 漢字 2 字節 |
| Big5 | 繁體中文標準 | 台灣/香港舊系統 | 漢字 2 字節 |
| UTF-8 | Unicode 變長編碼,兼容 ASCII | 現代 Web、數據庫、文件標準 | 英文1、漢字3 |
| UTF-16 | Java 內存內部編碼 | JVM 內部字符處理 | 2 或 4 字節 |
| ISO-8859-1 | 單字節西歐編碼,無中文 | HTTP 協議默認、舊系統中轉 | 1 |
[Java 源文件]
│ encoding=GBK/UTF-8
▼
[javac 編譯] ──► [.class 字節碼 (Unicode/UTF-8)]
│ -encoding 參數指定源文件編碼
▼
[JVM 類加載]
▼
[運行時字符串操作] ──► [String.getBytes(編碼) / new String(bytes, 編碼)]
│ 核心轉換點:字節[] ←→ char[]/String
▼
[I/O 流操作] ──► [InputStreamReader/OutputStreamWriter 指定 charset]
│ 文件/網絡/數據庫交互邊界
▼
[操作系統/終端/瀏覽器] ──► 顯示設備渲染
│ 環境默認編碼 (locale/Charset.defaultCharset())
// 字節數組 → 字符串 (解碼)
String str = new String(bytes, "UTF-8"); // 推薦
String str = new String(bytes, StandardCharsets.UTF_8); // JDK 7+
// 字符串 → 字節數組 (編碼)
byte[] bytes = str.getBytes("UTF-8"); // 推薦
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
// 流轉換 (高性能、大數據)
Reader reader = new InputStreamReader(inputStream, "UTF-8");
Writer writer = new OutputStreamWriter(outputStream, "UTF-8");
原因:Tomcat 8+ 默認 UTF-8,舊版本 GET 用 ISO-8859-1。
// 方案 A:Tomcat server.xml 統一設置 (推薦)
// 方案 B:Filter 統一處理 (Spring 已內置 CharacterEncodingFilter)
public class EncodingFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
chain.doFilter(req, resp);
}
}
// 方案 C:手動修復 (不推薦,治標不治本)
String fixed = new String(param.getBytes("ISO-8859-1"), "UTF-8");
核心原則:JDBC URL 指定編碼,數據庫/表/列編碼一致,連接池層面統一。
// MySQL 8.0+ 推薦配置
jdbc:mysql://localhost:3306/db?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
// 表創建指定編碼
CREATE TABLE t_user (
name VARCHAR(50)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
// 連接池 (HikariCP/Druid) 初始化設置
dataSource.setConnectionInitSql("SET NAMES utf8mb4");
// 讀取 GBK 文件
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream("gbk.txt"), "GBK"))) {
String line;
while ((line = br.readLine()) != null) { /* 處理 */ }
}
// 寫入 UTF-8 文件 (標準)
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("utf8.txt"), "UTF-8"))) {
bw.write("中文內容");
}
// JDK 7+ NIO 簡潔寫法
Files.readAllLines(Paths.get("gbk.txt"), Charset.forName("GBK"));
Files.write(Paths.get("utf8.txt"), "中文內容".getBytes(StandardCharsets.UTF_8));
// JSP 頁面指令 (必須在第一行)
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
// Servlet 手動設置
response.setContentType("text/html;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
// Spring MVC @ResponseBody 自動處理 (需配置 StringHttpMessageConverter)
@GetMapping("/api")
@ResponseBody
public String api() { return "中文"; } // 默認 UTF-8
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
| 現象 | 可能原因 | 排查命令/代碼 |
|---|---|---|
| 菱形問號 | UTF-8 字節流被 ISO-8859-1 解碼 | 檢查請求編碼、數據庫連接編碼 |
| 錘子碼 (涓婁竴) | GBK 字節流被 UTF-8 解碼 | 檢查頁面編碼聲明、文件讀取編碼 |
| ???? | 字符集不支持該字符 (如 latin1 存中文) | 檢查數據庫/表/列編碼、JDBC URL |
| 正常顯示但長度異常 | 雙重編碼/解碼 | 排查是否多次調用 getBytes/new String |
| 錯誤寫法 | 正確寫法 | 說明 |
|---|---|---|
| new String(bytes) | new String(bytes, StandardCharsets.UTF_8) | 無參使用平台默認編碼,不可移植 |
| str.getBytes() | str.getBytes(StandardCharsets.UTF_8) | 同上 |
| new String(str.getBytes("ISO-8859-1"), "GBK") | 避免雙重轉碼,源頭指定正確編碼 | 除非歷史系統對接,否則重構源頭 |
| request.getParameter() 直接用 | 配置 Filter + Tomcat URIEncoding | 統一入口解決,業務代碼零侵入 |
Java 中文亂碼的根治之道是全鏈路 UTF-8 標準化:源碼編碼、編譯編碼、運行時編碼、存儲編碼、傳輸編碼、顯示編碼——六統一。拒絕在業務代碼中手動轉碼,在邊界層 (Filter、連接池、序列化器) 統一攔截處理。遵循「字節流進出必指定編碼,內存中只操作 String」鐵律,亂碼將成為歷史。