中文字符編碼演進史與 PHP 實戰避坑指南
從 ASCII 到 Unicode,中文編碼經歷了單字節擴展、雙字節國標、繁體大五碼、國家擴展標準、統一碼多階段演進。本文系統梳理各編碼技術特徵、適用場景、PHP 中的亂碼根因與解決方案。
編碼演進時間軸
| 年份 | 標準 | 類型 | 字符集覆蓋 | 關鍵特徵 |
| 1963 | ASCII | 7-bit | 128 字符 | 僅英語、控制字符、標點 |
| 1981 | GB2312-80 | 雙字節 | 6763 漢字 + 符號 | 簡體中文國標,區位碼,99.75% 覆蓋率 |
| 1984 | BIG5 | 雙字節 | 13053 繁體字 | 台灣五大廠商聯發,含重碼,缺生僻字 |
| 1990+ | Unicode 1.0 | 16-bit/32-bit | 全球統一碼位 | 學術機構推動,碼位空間巨大 |
| 1993 | UTF-8 | 變長 1-4 字節 | Unicode 全覆蓋 | ASCII 兼容、網絡傳輸首選、無字節序問題 |
| 2000 | GB18030-2000 | 單/雙/四字節 | 27484 漢字 + 少數民族 | 國家強制標準、兼容 GB2312、與 Unicode 3.0 映射 |
| 2005 | Unicode 4.1 | — | 10 萬+ 字符 | 含 Emoji、歷史字符、擴展區 |
核心編碼技術對比
ASCII 與擴展 ASCII
- 標準 ASCII:7 位,0x00-0x7F (0-127)
- 擴展 ASCII:8 位,0x00-0xFF (0-255),高 128 個廠商自定義 (如 ISO-8859-1、CP437)
GB2312 (CP936 / EUC-CN)
- 編碼結構:雙字節,高位 0xA1-0xF7 (區號+0xA0),低位 0xA1-0xFE (位號+0xA0)
- 分區:01-09 區符號,16-55 區一級漢字(拼音序),56-87 區二級漢字(部首筆畫序)
- 示例:「啊」區位 1601 → 高字節 0xB0(16+0xA0),低字節 0xA1(1+0xA0) →
B0 A1
BIG5 (CP950)
- 高位:0xA1-0xF9,低位:0x40-0x7E、0xA1-0xFE
- 安排:符號 0xA140-0xA3BF,常用字 0xA440-0xC67E (筆畫序),次常用 0xC940-0xF9D5
- 缺陷:重碼(兀、嗀)、缺生僻人名字、無日文假名
GB18030 (CP54936)
- 三模式:單字節(0x00-0x7F 兼容 ASCII)、雙字節(高 0x81-0xFE,低 0x40-0x7E/0x80-0xFE)、四字節(擴展區,前綴 0x30-0x39)
- 收錄:27484 漢字,含 CJK 擴展 A、少數民族文字、日韓字符
- 強制性:2001 年後中國市場軟件必須支援
Unicode 與 UTF 族
| 方案 | 碼位範圍 | 字節長度 | 特點 |
| UTF-8 | U+0000 - U+10FFFF | 1-4 字節 (變長) | ASCII 兼容、無字節序、網絡標準、MySQL 需用 utf8mb4 |
| UTF-16 | U+0000 - U+10FFFF | 2 或 4 字節 | 需 BOM 區分大小端、Java/JS/WinAPI 內部用 |
| UTF-32 | U+0000 - U+10FFFF | 固定 4 字節 | 直接索引碼位、佔空間大、內部處理用 |
PHP 中的編碼亂碼根因與解決層級
層級 1:HTTP 響應頭 (最高優先級)
<?php
// 必須在任何輸出前
header('Content-Type: text/html; charset=UTF-8');
// 或
header('Content-Type: application/json; charset=UTF-8');
覆蓋 HTML meta、Apache 默認字符集、PHP 內部默認字符集。
層級 2:HTML Meta 標簽
<meta charset="UTF-8">
<!-- HTML5 簡寫 -->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<!-- 兼容寫法 -->
層級 3:Apache 配置
# httpd.conf 或 .htaccess
AddDefaultCharset UTF-8
# 或註釋掉讓 PHP/HTML 控制
# AddDefaultCharset Off
層級 4:PHP 內部默認字符集
; php.ini
default_charset = "UTF-8"
; 影響 htmlentities、htmlspecialchars、json_encode 等默認編碼
層級 5:資料庫連接字符集
<?php
// PDO (推薦)
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', $user, $pass);
// mysqli
$mysqli->set_charset('utf8mb4');
// 避免使用 SET NAMES (不影響 PDO 內部狀態)
層級 6:文件源碼編碼
- 編輯器保存為 UTF-8 無 BOM
- PHP 解析器按文件編碼讀取字符串字面量
優先級順序 (高 → 低)
1. PHP header() ← 代碼控制,最可靠
2. Apache AddDefaultCharset
3. HTML
4. PHP default_charset
5. 瀏覽器自動檢測
常見亂碼場景速查表
| 現象 | 原因 | 解決 |
| 中文顯示 | UTF-8 頁面被 GBK 解碼 | 統一聲明 UTF-8,檢查 header/meta |
| 中文顯示 鏂伴椈 | GBK 頁面被 UTF-8 解碼 | 同上 |
| 入庫變 ??? | 連接字符集非 utf8mb4 | PDO DSN 加 charset=utf8mb4 |
| Emoji 存不進/變 ? | MySQL utf8 只支援 3 字節 | 表/庫/連接全鏈路 utf8mb4 |
| json_encode 變 \uXXXX | 默認轉義非 ASCII | JSON_UNESCAPED_UNICODE |
| curl 獲取亂碼 | 遠程編碼非 UTF-8 | iconv/mb_convert_encoding 轉碼 |
PHP 編碼轉換函數速查
<?php
// 多字節字符串轉換 (需 mbstring)
mb_convert_encoding($str, 'UTF-8', 'GBK'); // 指定源編碼
mb_convert_encoding($str, 'UTF-8', 'auto'); // 自動檢測 (不準確)
// iconv (系統級,支援更多編碼)
iconv('GBK', 'UTF-8//IGNORE', $str); // //IGNORE 忽略非法字符
iconv('UTF-8', 'GBK//TRANSLIT', $str); // //TRANSLIT 近似轉換
// 文件編碼批量轉換 (Linux)
# find . -name "*.php" -exec iconv -f GBK -t UTF-8 {} -o {}.utf8 \;
最佳實踐總結
- 全棧 UTF-8:源碼、HTML、HTTP Header、PHP default_charset、資料庫連接、表字符集
- MySQL 必須用 utf8mb4:
CREATE DATABASE ... CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
- 入口統一聲明:框架入口文件
header('Content-Type: ...; charset=UTF-8')
- 外部數據轉碼:第三方 API、爬蟲、舊系統數據,明確源編碼用
mb_convert_encoding
- 避免 BOM:UTF-8 無 BOM,防止輸出前空白導致 header 失效