最近在寫網(wǎng)絡(luò)數(shù)據(jù)傳輸?shù)某绦?,被各種編碼搞的一塌糊涂,在這里簡單記錄如下:
1. ASCII和Ansi編碼
字符內(nèi)碼(charcter code)指的是用來代表字符的內(nèi)碼.讀者在輸入和存儲文檔時都要使用內(nèi)碼,內(nèi)碼分為
a.單字節(jié)內(nèi)碼 -- Single-Byte character sets (SBCS),可以支持256個字符編碼.
b.雙字節(jié)內(nèi)碼 -- Double-Byte character sets (DBCS),可以支持65000個字符編碼.
前者即為ASCII編碼,后者對應(yīng)ANSI。在簡體中文的操作系統(tǒng)中ANSI就指的是GB2312,代碼頁936(ANSI下不同語言有不同的代碼頁)。
2.GB2312和GBK編碼
GB2312是對 ANSI 的簡體中文擴展。GB2312共收錄了七千個字符,由于GB2312支持的漢字太少而且不支持繁體中文,所以GBK對GB2312進行了擴展,以支持繁體中文和更多的字符,GBK共支持大概22000個字符,GB18030是在GBK的基礎(chǔ)上又增加了藏文、蒙文、維吾爾文等主要的少數(shù)民族文字。
代碼頁(codepage) 就是各國的文字編碼和Unicode之間的映射表。例如GBK和Unicode的映射表就是CP936,所以也常用cp936 來指代GBK。
3.Unicode
ANSI有很多代碼頁,使用不同代碼頁的內(nèi)碼無法在其他代碼頁平臺上正常顯示。由于各國之間的編碼不同造成的交流傳輸不便,ISO 打算廢除所有的地區(qū)性編碼方案,重新建立一個全球性的編碼方案把所有字母和符號都統(tǒng)一編碼進去,稱之為 "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位(一個字節(jié))的UTF編碼方式,一個字符可能會經(jīng)過1-6次傳輸,具體的跟 unicode/UCS 之間的轉(zhuǎn)換關(guān)系如下:
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之間,所以采用三字節(jié)編碼,按規(guī)則分段為:0110 001000 010001,再分別替換上表中的x,得到11100110 10001000 10010001,即為 "E6 88 91",這就是 "我" 的UTF-8編碼。
舉個有趣的例子:
在 Windows 的記事本里新建一個文本文件,輸入"聯(lián)通"兩個字,保存,關(guān)閉,再次打開,會發(fā)現(xiàn)文本已經(jīng)不是"聯(lián)通"了,而是幾個亂碼。
當(dāng)使用記事本新建文件時,默認(rèn)的編碼是 ANSI,輸入中文就是 GB 系列的編碼,"聯(lián)通" 兩字的編碼為:
c1 1100 0001
aa 1010 1010
cd 1100 1101
a8 1010 1000
注意到了嗎?第一二個字節(jié)、第三四個字節(jié)的起始部分的都是 "110" 和 "10",正好與 UTF-8 規(guī)則里的兩字節(jié)模板是一致的,于是再次打開記事本時,記事本就誤認(rèn)為這是一個UTF-8編碼的文件,讓我們把第一個字節(jié)的110和第二個字節(jié)的10去掉,我們就得到了"00001 101010",再把各位對齊,補上前導(dǎo)的0,就得到了 "0000 0000 0110 1010",這是 UNICODE 的 006A,也就是小寫的字母 "j",而之后的兩字節(jié)用 UTF-8 解碼之后是0368,這個字符什么也不是。這就是只有 "聯(lián)通" 兩個字的文件沒有辦法在記事本里正常顯示的原因。
而如果你在 "聯(lián)通" 之后多輸入幾個其他字,其他的字的編碼不見得又恰好是 110 和 10 開始的字節(jié),這樣再次打開時,記事本就不會堅持這是一個 UTF-8 編碼的文件,而會用 ANSI 的方式解讀之,這時亂碼又不出現(xiàn)了。
5.UTF-16
UTF-16是一次傳輸兩個字節(jié)的UTF編碼方式,現(xiàn)如今Unicode/UCS也主要采用16位編碼,所以UTF-16的存儲方式和Unicode/UCS的編碼方式也相同。確切的說是和UCS-2/unicode 16的編碼方式相同。
6.big endian 和 little endian
在UTF-16或者UCS的編碼中經(jīng)常遇到這兩個選項,big endian 和little endian 是CPU處理多字節(jié)數(shù)的不同方式。例如“漢”字的 Unicode/UCS 編碼是 6C49。那么寫到文件里時,究竟是將 6C 寫在前面,還是將 49 寫在前面?如果將 6C 寫在前面,就是big endian。還是將 49 寫在前面,就是little endian。
BOM 稱為 "Byte Order Mark"。UTF-8 以字節(jié)為編碼單元,沒有字節(jié)序的問題。而 UTF-16 以兩個字節(jié)為編碼單元,在解釋一個 UTF-16 文本前,首先要弄清楚每個編碼單元的字節(jié)序。例如收到一個 "奎" 的 Unicode/UCS 編碼是 594E,"乙" 的 Unicode/UCS 編碼是 4E59。如果我們收到 UTF-16 字節(jié)流 "594E",那么這是 "奎" 還是 "乙"?
在Unicode/UCS編碼中有一個叫做 "ZERO WIDTH NO-BREAK SPACE" 的字符,它的編碼是FEFF。而FFFE在Unicode/UCS中是不存在的字符,所以不應(yīng)該出現(xiàn)在實際傳輸中。UCS規(guī)范建議我們在傳輸字節(jié)流前,先傳輸字符 "ZERO WIDTH NO-BREAK SPACE"。這樣如果接收者收到 FEFF,就表明這個字節(jié)流是Big-Endian 的;如果收到FFFE,就表明這個字節(jié)流是Little-Endian 的。因此字符 "ZERO WIDTH NO-BREAK SPACE" 又被稱作 BOM。
UTF-8 不需要 BOM 來表明字節(jié)順序,但可以用 BOM 來表明編碼方式。字符 "ZERO WIDTH NO-BREAK SPACE" 的 UTF-8 編碼是 EF BB BF。所以如果接收者收到以 EF BB BF 開頭的字節(jié)流,就知道這是 UTF-8 編碼了。Windows 就是使用 BOM 來標(biāo)記文本文件的編碼方式的。