最近在寫網路資料轉送的程式,被各種編碼搞的一塌糊塗,在這裡簡單記錄如下:
1. ASCII和Ansi編碼
字元內碼(charcter code)指的是用來代表字元的內碼.讀者在輸入和儲存文檔時都要使用內碼,內碼分為
a.單位元組內碼 -- Single-Byte character sets (SBCS),可以支援256個字元編碼.
b.雙位元組內碼 -- Double-Byte character sets (DBCS),可以支援65000個字元編碼.
前者即為ASCII編碼,後者對應ANSI。在簡體中文的作業系統中ANSI就指的是GB2312,字碼頁936(ANSI下不同語言有不同的字碼頁)。
2.GB2312和GBK編碼
GB2312是對 ANSI 的簡體中文擴充。GB2312共收錄了七千個字元,由於GB2312支援的漢字太少而且不支援繁體中文,所以GBK對GB2312進行了擴充,以支援繁體中文和更多的字元,GBK共支援大概22000個字元,GB18030是在GBK的基礎上又增加了藏文、蒙文、維吾爾文等主要的少數民族文字。
字碼頁(codepage) 就是各國的文字編碼和Unicode之間的映射表。例如GBK和Unicode的映射表就是CP936,所以也常用cp936 來指代GBK。
3.Unicode
ANSI有很多字碼頁,使用不同字碼頁的內碼無法在其他字碼頁平台上正常顯示。由於各國之間的編碼不同造成的交流傳輸不便,ISO 打算廢除所有的地區性編碼方案,重建立立一個全球性的編碼方案把所有字母和符號都統一編碼進去,稱之為 "Universal Multiple-Octet Coded Character Set",簡稱為 UCS(ISO10646)。同時又有unicode.org這個組織也制定了自己的全球性編碼 unicode,自從unicode2.0開始,unicode採用了與USC相同的字型檔和字碼,階段主要採用的是 UCS-2/unicode 16 位的編碼。
4.UTF編碼
UTF(Unicode/UCS Transfer Format),UCS 變長儲存的編碼方式,主要用來解決 UCS 編碼的傳輸問題的。分為 UTF-7,UTF-8,UTF-16,UTF-32 等。UTF-8是一次傳輸8位(一個位元組)的UTF編碼方式,一個字元可能會經過1-6次傳輸,具體的跟 unicode/UCS 之間的轉換關係如下:
| unicode(U+) |
utf-8 |
| U+00000000 - U+0000007F: |
0xxxxxxx |
| U+00000080 - U+000007FF: |
110xxxxx10xxxxxx |
| U+00000800 - U+0000FFFF: |
1110xxxx10xxxxxx10xxxxxx |
| U+00010000 - U+001FFFFF: |
11110xxx10xxxxxx10xxxxxx10xxxxxx |
| U+00200000 - U+03FFFFFF: |
111110xx10xxxxxx10xxxxxx10xxxxxx10xxxxxx |
| U+04000000 - U+7FFFFFFF: |
1111110x10xxxxxx10xxxxxx10xxxxxx10xxxxxx10xxxxxx |
比如: "我" 的unicode/UCS編碼為 "U+6211"(01100010 00010001),在U+00000800 - U+0000FFFF之間,所以採用三位元組編碼,按規則分段為:0110 001000 010001,再分別替換上表中的x,得到11100110 10001000 10010001,即為 "E6 88 91",這就是 "我" 的UTF-8編碼。
舉個有趣的例子:
在 Windows 的記事本裡建立一個文字檔,輸入"聯通"兩個字,儲存,關閉,再次開啟,會發現文本已經不是"聯通"了,而是幾個亂碼。
當使用記事本建立檔案時,預設的編碼是 ANSI,輸入中文就是 GB 系列的編碼,"聯通" 兩字的編碼為:
c1 1100 0001
aa 1010 1010
cd 1100 1101
a8 1010 1000
注意到了嗎?第一二個位元組、第三四個位元組的起始部分的都是 "110" 和 "10",正好與 UTF-8 規則裡的兩位元組模板是一致的,於是再次開啟記事本時,記事本就誤認為這是一個UTF-8編碼的檔案,讓我們把第一個位元組的110和第二個位元組的10去掉,我們就得到了"00001 101010",再把各位對齊,補上前置的0,就得到了 "0000 0000 0110 1010",這是 UNICODE 的 006A,也就是小寫字母 "j",而之後的兩位元組用 UTF-8 解碼之後是0368,這個字元什麼也不是。這就是只有 "聯通" 兩個字的檔案沒有辦法在記事本裡正常顯示的原因。
而如果你在 "聯通" 之後多輸入幾個其他字,其他的字的編碼不見得又恰好是 110 和 10 開始的位元組,這樣再次開啟時,記事本就不會堅持這是一個 UTF-8 編碼的檔案,而會用 ANSI 的方式解讀之,這時亂碼又不出現了。
5.UTF-16
UTF-16是一次傳輸兩個位元組的UTF編碼方式,現如今Unicode/UCS也主要採用16位編碼,所以UTF-16的儲存方式和Unicode/UCS的編碼方式也相同。確切的說是和UCS-2/unicode 16的編碼方式相同。
6.big endian 和 little endian
在UTF-16或者UCS的編碼中經常遇到這兩個選項,big endian 和little endian 是CPU處理多位元組數的不同方式。例如“漢”字的 Unicode/UCS 編碼是 6C49。那麼寫到檔案裡時,究竟是將 6C 寫在前面,還是將 49 寫在前面?如果將 6C 寫在前面,就是big endian。還是將 49 寫在前面,就是little endian。
BOM 稱為 "Byte Order Mark"。UTF-8 以位元組為編碼單元,沒有位元組序的問題。而 UTF-16 以兩個位元組為編碼單元,在解釋一個 UTF-16 文本前,首先要弄清楚每個編碼單元的位元組序。例如收到一個 "奎" 的 Unicode/UCS 編碼是 594E,"乙" 的 Unicode/UCS 編碼是 4E59。如果我們收到 UTF-16 位元組流 "594E",那麼這是 "奎" 還是 "乙"?
在Unicode/UCS編碼中有一個叫做 "ZERO WIDTH NO-BREAK SPACE" 的字元,它的編碼是FEFF。而FFFE在Unicode/UCS中是不存在的字元,所以不應該出現在實際傳輸中。UCS規範建議我們在傳輸位元組流前,先傳輸字元 "ZERO WIDTH NO-BREAK SPACE"。這樣如果接收者收到 FEFF,就表明這個位元組流是Big-Endian 的;如果收到FFFE,就表明這個位元組流是Little-Endian 的。因此字元 "ZERO WIDTH NO-BREAK SPACE" 又被稱作 BOM。
UTF-8 不需要 BOM 來表明位元組順序,但可以用 BOM 來表明編碼方式。字元 "ZERO WIDTH NO-BREAK SPACE" 的 UTF-8 編碼是 EF BB BF。所以如果接收者收到以 EF BB BF 開頭的位元組流,就知道這是 UTF-8 編碼了。Windows 就是使用 BOM 來標記文字檔的編碼方式的。