編碼體系速查表
| 編碼 | 類型 | 字元範圍 | 中文支援 | 相容性 |
| ISO-8859-1 | 單位元組 | 0-255 | 無 | ASCII 超集,HTTP 預設 |
| GB2312 | 雙位元組 | 6763 漢字 | 簡體 | 相容 ISO-8859-1 (英文) |
| GBK | 雙位元組 | 21000+ 漢字 | 簡繁體 | 相容 GB2312、ISO-8859-1 |
| Unicode (UTF-16) | 定長雙位元組 | 所有字元 | 全支援 | 不相容 ISO-8859-1 |
| UTF-8 | 變長 1-4 位元組 | 所有字元 | 全支援 | 相容 ASCII (0-127) |
「中文」二字編碼實例
字元: 中 文
GBK: D6 D0 CE C4
Unicode: 4E 2D 65 87
UTF-8: E4 B8 AD E6 96 87
ISO-8859-1: 無法表示 (若強制按位元組解讀: D6 D0 CE C4 → 4 個亂碼字元)
Java 核心 API 轉換
String.getBytes(charset) : String → byte[]
String s = "中文";
// 正確編碼
byte[] gbk = s.getBytes("GBK"); // [D6, D0, CE, C4]
byte[] utf8 = s.getBytes("UTF-8"); // [E4, B8, AD, E6, 96, 87]
// 錯誤編碼
byte[] iso = s.getBytes("ISO-8859-1"); // [3F, 3F] 兩個 '?' (0x3F)
new String(bytes, charset) : byte[] → String
// 正確解碼 (可逆)
String s1 = new String(gbk, "GBK"); // "中文" ✓
String s2 = new String(utf8, "UTF-8"); // "中文" ✓
// 錯誤解碼 (不可逆)
String s3 = new String(iso, "ISO-8859-1"); // "??" ✗
String s4 = new String(gbk, "UTF-8"); // 亂碼 ✗
完全可逆原則
// UTF-8 可表示所有字元,故完全可逆
String original = "中文English123";
String roundtrip = new String(original.getBytes("UTF-8"), "UTF-8");
assert original.equals(roundtrip); // 永真
Web 應用編碼處理鏈路
表單提交流程
用戶輸入(GBK) → 瀏覽器編碼(GBK) → HTTP 請求位元組流
→ Tomcat 預設 ISO-8859-1 解碼 → getParameter() 亂碼
→ request.setCharacterEncoding("GBK") → 正確解碼
關鍵設定點總表
| 層級 | 設定方式 | 作用 | 優先級 |
| JSP 編譯 | <%@ page pageEncoding="GBK" %> | 原始檔讀取編碼 | 高 |
| JSP 輸出 | <%@ page contentType="text/html;charset=GBK" %> | 回應編碼 + 告知瀏覽器 | 高 |
| Servlet 請求 | request.setCharacterEncoding("UTF-8") | POST 參數解碼 (需在 getParameter 前) | 高 |
| Servlet 回應 | response.setCharacterEncoding("UTF-8") | 輸出編碼 + Content-Type | 高 |
| HTML Meta | <meta charset="UTF-8"> | 靜態頁面告知瀏覽器 | 中 |
| 表單屬性 | <form accept-charset="UTF-8"> | 提交編碼 (瀏覽器支援度不一) | 低 |
| Tomcat 全域 | <Connector URIEncoding="UTF-8" /> | GET 參數解碼 (全域生效) | 中 |
| 資料庫連線 | useUnicode=true&characterEncoding=UTF-8 | JDBC 連線編碼 | 高 |
Filter 統一編碼 (推薦)
@WebFilter("/*")
public class EncodingFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
chain.doFilter(req, resp);
}
}
URL 編碼地獄與解決方案
問題根源
- IE「總是使用 UTF-8 發送 URL」選項影響編碼
- GET 參數在 URL 中,
request.setCharacterEncoding() 無效
- Tomcat 預設 ISO-8859-1 解碼 URL
UTF-8 判斷算法
public static boolean isValidUtf8(byte[] bytes) {
int i = 0;
while (i < bytes.length) {
byte b = bytes[i++];
if (b >= 0) continue; // ASCII
if (b < (byte)0xC0 || b > (byte)0xFD) return false;
int count = (b > (byte)0xFC) ? 5 : (b > (byte)0xF8) ? 4
: (b > (byte)0xF0) ? 3 : (b > (byte)0xE0) ? 2 : 1;
if (i + count > bytes.length) return false;
for (int j = 0; j < count; j++) {
if (bytes[i++] < (byte)0x80) return false; // 續位元組必須 10xxxxxx
}
}
return true;
}
// 使用
String param = new String(bytes, isValidUtf8(bytes) ? "UTF-8" : "GBK");
缺陷
- 誤判:GBK 「學習」 (
D1 A7 CF B0) 符合 UTF-8 格式 → 誤判為 UTF-8
- Google 同樣遇此問題,無完美方案
資料庫與系統層編碼
MySQL
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
# 連線 URL
jdbc:mysql://host/db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
Linux 環境變數
export LANG=zh_CN.UTF-8
export LC_ALL=zh_CN.UTF-8
# 影響 JVM 預設編碼、URLEncoder、檔案系統
檔案系統掛載
# NTFS 支援中文檔名
mount -t ntfs -o iocharset=gb2312 /dev/sda1 /mnt/data
常見亂碼場景診斷表
| 現象 | 可能原因 | 解決 |
| ??? 或 | 編碼不支援字元 (如 ISO-8859-1 存中文) | 全鏈路 UTF-8 |
| 鍟斤拷 | UTF-8 被 GBK 解碼 | 統一 UTF-8 |
| 涓婁竴 | GBK 被 UTF-8 解碼 | 統一 GBK 或轉 UTF-8 |
| 中文變成 ?? | ISO-8859-1 強制編碼 | 設定正確編碼 |
| 檔案開頭 3 個不可見字元 | UTF-8 BOM (EF BB BF) | 用無 BOM 編碼儲存 |
總結:編碼統一鐵律
- 全棧 UTF-8:原始碼、資料庫、JVM、Tomcat、瀏覽器、檔案系統
- 原始檔無 BOM:IDE 設定 UTF-8 without BOM
- Filter 統一請求/回應編碼
- 資料庫用 utf8mb4:支援 Emoji/生僻字
- GET 參數慎用,優先 POST + JSON
- URL 編碼指定 UTF-8:
URLEncoder.encode(s, "UTF-8")
編碼問題本質是發送端與接收端約定不一致。建立團隊編碼規範、CI 檢查原始檔編碼、統一基礎設施配置,比事後除錯高效百倍。