中文亂碼是 Java 開發者的經典痛點。本質上是位元組序列與字元編碼不匹配導致。理解 JVM 內部使用 UTF-16 (char),而外部世界(檔案、網路、資料庫、瀏覽器)使用各種編碼(UTF-8, GBK, Big5, ISO-8859-1),掌握解碼/編碼邊界,即可徹底解決。
char / String:JVM 內部 **UTF-16** 編碼(2-4 位元組/字元)byte[]:外部儲存/傳輸形式,**編碼相關**(UTF-8: 1-4 bytes, GBK: 1-2 bytes)| 方向 | 類別 | 核心方法 |
|---|---|---|
| Byte → Char (解碼) | InputStreamReader, ByteToCharConverter | read(), convertAll() |
| Char → Byte (編碼) | OutputStreamWriter, CharToByteConverter | write(), convertAll() |
// 解碼:GBK 位元組 → Unicode 字元
byte[] gbkBytes = {(byte)0xC4, (byte)0xE3}; // "你" 的 GBK 編碼
String encoding = "GBK";
ByteToCharConverter converter = ByteToCharConverter.getConverter(encoding);
char[] chars = converter.convertAll(gbkBytes); // 結果:['你'] (0x4F60)
// 編碼:Unicode 字元 → UTF-8 位元組
char[] uniChars = {'你'}; // 0x4F60
CharToByteConverter conv = CharToByteConverter.getConverter("UTF-8");
byte[] utf8Bytes = conv.convertAll(uniChars); // 結果:[0xE4, 0xBD, 0xA0]
characterEncoding、useUnicode=truenew FileReader(file) 使用平台預設編碼 → **必須用** new InputStreamReader(new FileInputStream(file), "UTF-8")Content-Type: text/html; charset=UTF-8request.setCharacterEncoding("UTF-8")(需在讀取參數前呼叫)response.setContentType("text/html;charset=UTF-8") 或 response.setCharacterEncoding("UTF-8")<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>javac -encoding UTF-8(否則中文常值依平台編碼編譯,跨平台亂碼)
// 錯:用平台預設編碼(中文 Windows 為 GBK,Linux 為 UTF-8)
byte[] bytes = str.getBytes();
// 正:明確指定
byte[] bytes = str.getBytes(StandardCharsets.UTF_8); // Java 7+
// 錯
String s = new String(bytes);
// 正
String s = new String(bytes, StandardCharsets.UTF_8);
// 錯:無法指定編碼
FileReader fr = new FileReader("file.txt");
FileWriter fw = new FileWriter("out.txt");
// 正:使用 Stream + Reader/Writer 包裝
try (InputStreamReader isr = new InputStreamReader(new FileInputStream("file.txt"), "UTF-8");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("out.txt"), "UTF-8")) {
// ...
}
server.xml 設定 <Connector URIEncoding="UTF-8" useBodyEncodingForURI="true" />
Unicode 碼位 → UTF-8 位元組序列:
| Unicode 範圍 | 位元組數 | 位元格式 | 範例 |
|---|---|---|---|
| U+0000 ~ U+007F | 1 | 0xxxxxxx | ASCII 相容 |
| U+0080 ~ U+07FF | 2 | 110xxxxx 10xxxxxx | 希臘文、西里爾文 |
| U+0800 ~ U+FFFF | 3 | 1110xxxx 10xxxxxx 10xxxxxx | 中日韓文字 (CJK) |
| U+10000 ~ U+10FFFF | 4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | Emoji、罕用字 |
二進制:0100 1111 0110 0000 (16 bits) 分組: 0100 111101 100000 填入: 11100100 10111101 10100000 十六進制:E4 BD A0
@WebFilter("/*")
public class EncodingFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
req.setCharacterEncoding("UTF-8");
res.setCharacterEncoding("UTF-8");
res.setContentType("text/html;charset=UTF-8");
chain.doFilter(req, res);
}
}
# application.yml
server:
servlet:
encoding:
charset: UTF-8
enabled: true
force: true
force-request: true
force-response: true
<Connector port="8080" protocol="HTTP/1.1"
URIEncoding="UTF-8"
useBodyEncodingForURI="true" />
# MySQL 連接字串
jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Taipei
# 資料庫/資料表/欄位編碼一致性
CREATE DATABASE db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE t (c VARCHAR(255)) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
關鍵:使用 utf8mb4 而非 utf8(MySQL 的 utf8 只有 3 位元組,不支援 Emoji)。
中文亂碼本質是編碼協議不一致。核心原則:**邊界處明確指定編碼,全鏈路統一使用 UTF-8 (或 utf8mb4)**。Java 7+ 提供 StandardCharsets 常數,Java 18+ file.encoding 預設 UTF-8,現代開發環境已大幅減少亂碼機率,但理解底層原理仍是排查疑難雜症的關鍵。