亂碼是開發者的噩夢。無論是讀取文件、網頁顯示、資料庫存儲,還是 API 串接,編碼不一致都會導致數據損壞。本文從歷史演進角度,梳理 ASCII、擴展編碼、Unicode 到 UTF-8 的來龍去脈,並給出 C# 實戰代碼。
1960 年代美國制定的 ASCII 碼,用 7 位元表示 128 個字符(含 32 個控制字符),最高位固定為 0。例如:空格 32 (00100000)、大寫 A 65 (01000001)、小寫 a 97 (1100001)。
string s = "a";
byte[] ascii = Encoding.ASCII.GetBytes(s); // [97]
歐洲語言需要變音符號,便利用第 8 位擴展到 256 個字符。但同一個字節值在不同語系代表不同字符:130 在法語是 é,希伯來語卻是 Gimel,俄語又不同。中文更嚴重,漢字超過 10 萬,必須用多字節編碼(如 GB2312 雙字節,理論容納 65536 字符)。
string s = "梁";
Encoding gb2312 = Encoding.GetEncoding("GB2312");
byte[] gb = gb2312.GetBytes(s); // [193, 186]
Unicode 為全球所有字符分配唯一碼位(目前超過 100 萬),解決「同碼異字」問題。但它只定義碼位,不定義存儲方式。
string s = "梁";
byte[] unicode = Encoding.Unicode.GetBytes(s); // [129, 104] (小端序 0x6881)
導致出現多種存儲格式(UTF-8、UTF-16、UTF-32),且 Unicode 長期難以推廣,直到互聯網普及。
UTF-8 是 Unicode 的一種變長實現:1-4 字節動態編碼。
string s = "梁";
byte[] utf8 = Encoding.UTF8.GetBytes(s); // [230, 162, 129] = 0xE6 0xA2 0x81
Unicode 0x6881 落在 U+0800~U+FFFF 區間,採用 3 字節模板 1110xxxx 10xxxxxx 10xxxxxx,從低位向高位填入得 11100110 10100010 10000001。
string UTF8ToGb2312(string str)
{
Encoding utf8 = Encoding.UTF8;
Encoding gb2312 = Encoding.GetEncoding("gb2312");
byte[] unicodeBytes = utf8.GetBytes(str);
byte[] gbBytes = Encoding.Convert(utf8, gb2312, unicodeBytes);
char[] chars = new char[gb2312.GetCharCount(gbBytes, 0, gbBytes.Length)];
gb2312.GetChars(gbBytes, 0, gbBytes.Length, chars, 0);
return new string(chars);
}
| 編碼 | 字節長度 | 適用場景 | C# 類 |
|---|---|---|---|
| ASCII | 1 字節 | 純英文 | Encoding.ASCII |
| GB2312 | 2 字節 | 簡體中文舊系統 | Encoding.GetEncoding("GB2312") |
| Unicode (UTF-16LE) | 2/4 字節 | .NET 內存內部 | Encoding.Unicode |
| UTF-8 | 1-4 字節 | Web、API、現代應用 | Encoding.UTF8 |
理解編碼本質,亂碼就不再是不可解之謎。