天天看點

關于檔案編碼格式的一點探讨

以前碰到過檔案編碼問題,但都沒太在意。最近在win7下寫一個c++程式時,轉移到linux下表現怪異,調試個半天發現竟然是檔案編碼問題!于是想花點時間好好總結一下關于檔案編碼格式的基本概念。這東西長時間沒搞就容易忘,這也友善以後再來查詢。

關于檔案編碼格式的一點探讨

ascii

ascii碼于1961年提出,用于在不同計算機硬體和軟體系統中實作資料傳輸标準化,在大多數的小型機和全部的個人計算機都使用此碼。ascii碼劃分為兩個集合:128個字元的标準ascii碼和附加的128個字元的擴充ascii碼。标準ascii碼為7位,擴充為8位。擴充的8位碼官方标準叫:iso-8859-1,也叫latin-1編碼

ansi(mbcs)

為了擴充ascii編碼,以用于顯示本國的語言,不同的國家和地區制定了不同的标準,由此産生了 gb2312, big5, jis 等各自的編碼标準。這些使用2個位元組來代表一個字元的各種漢字延伸編碼方式,稱為ansi編碼,又稱為"mbcs(multi-bytes character set,多位元組字元集)"。在簡體中文系統下,ansi編碼代表gb2312編碼,在日文作業系統下,ansi 編碼代表 jis

編碼,是以在中文 windows下要轉碼成gb2312,gbk隻需要把文本儲存為ansi編碼即可。不同ansi編碼之間互不相容,導緻了unicode碼的誕生。

我國的編碼

gb2312,1980年頒布,有區碼、位碼、存儲碼之分

gbk,相容gb2312,添加了繁體字

gb18030,增加了更多字元

big5,台灣、香港地區使用,也叫大五碼

unicode

統一了全世界的所有字元,可分為ucs-2(對應utf-16)和ucs-4(對應utf-32),有大小端之分,如"a"的unicode編碼為6500,而bigendian unicode編碼為0065,編碼效率不高

utf

為了提高unicode編碼效率,就出現了utf碼,變長位元組。utf-8相容ascii,utf-16不相容ascii。

ucs-2與utf-8位元組流之間的對應

ucs-2編碼(16進制)   utf-8 位元組流(二進制)

0000 - 007f         0xxxxxxx

0080 - 07ff         110xxxxx10xxxxxx

0800 - ffff         1110xxxx10xxxxxx 10xxxxxx

例如“漢”字的unicode編碼是6c49。6c49在0800-ffff之間,是以肯定要用3位元組模闆了:1110xxxx10xxxxxx 10xxxxxx。将6c49寫成二進制是:0110 110001 001001,用這個比特流依次代替模闆中的x,得到:11100110 10110001 10001001,即e6 b1 89。可見utf-8是變長的,将unicode編碼為0000-0007f的字元,用單個位元組來表示; 0080-007ff的字元用兩個位元組表示;0800-ffff的字元用3位元組表示。因為目前為止unicode-16規範沒有指定ffff以上的字元,是以utf-8最多是使用3個位元組來表示一個字元。

位元組序

utf-8以位元組為編碼單元,沒有位元組序的問題。utf-16以兩個位元組為編碼單元,在解釋一個utf-16文本前,首先要弄清楚每個編碼單元的位元組序。例如收到一個“奎”的unicode編碼是594e,“乙”的unicode編碼是4e59。如果我們收到utf-16位元組流“594e”,那麼這是“奎”還是“乙”?

unicode規範中推薦的标記位元組順序的方法是bom。bom不是“bill of material”的bom表,而是byte order mark。bom是一個有點小聰明的想法:

在ucs編碼中有一個叫做"zerowidth no-break space"的字元,它的編碼是feff。而fffe在ucs中是不存在的字元,是以不應該出現在實際傳輸中。ucs規範建議我們在傳輸位元組流前,先傳輸字元"zero width no-break space"。

這樣如果接收者收到feff,就表明這個位元組流是big-endian的;如果收到fffe,就表明這個位元組流是little-endian的。是以字元"zerowidth

no-break space"又被稱作bom。

utf-8不需要bom來表明位元組順序,但可以用bom來表明編碼方式。字元"zero width no-break space"的utf-8編碼是ef bb bf。是以如果接收者收到以ef bb bf開頭的位元組流,就知道這是utf-8編碼了

不同的系統對bom的支援

因為一些系統或程式不支援bom,是以帶有bom的unicode檔案有時會帶來一些問題。

1、jdk1.5以及之前的reader都不能處理帶有bom的utf-8編碼的檔案,解析這種格式的xml檔案時,會抛出異常:content is not allowed in prolog。

2、linux/unix 并沒有使用 bom,因為它會破壞現有的 ascii 檔案的文法約定。

3、不同的編輯工具對bom的處理也各不相同。使用windows自帶的記事本将檔案儲存為utf-8編碼的時候,記事本會自動在檔案開頭插入bom(雖然bom對utf-8來說并不是必須的)。而其它很多編輯器用不用bom是可以選擇的。utf-8、utf-16都是如此。

軟體如何決定文本的字元集與編碼

(1)對于unicode文本最标準的途徑是檢測文本最開頭的幾個位元組。如:

開頭位元組       charset/encoding

 ef bb bf    utf-8

 fe ff      utf-16/ucs-2,big endian(utf-16be)

 ff fe      utf-16/ucs-2,little endian(utf-16le)

 ff fe 00 00  utf-32/ucs-4,little endian.

 00 00 fe ff  utf-32/ucs-4,big-endian

(2)彈出一個對話框來請示使用者。

然而mbcs文本(ansi)沒有這些位于開頭的字元集标記,現在很多軟體儲存文本為unicode時,可以選擇是否儲存這些位于開頭的字元集标記。是以,軟體不應該依賴于這種途徑。這時,軟體可以采取一種比較安全的方式來決定字元集及其編碼,那就是彈出一個對話框來請示使用者。

(3)采取自己“猜”的方法。

win7平台測試示例

win7下建立一個檔案tmp.txt,内容為:

a<cr>

即一個ascii字元,一換行,再加一中文字元。

我們知道在中文windows系統中,預設ansi即為gb系列的字元集,不妨将tmp.txt更名為tmp-ansi.txt。那怎麼查呢?可用emeditor打開它,在視窗右下角即可見所屬字元集,如下圖:

關于檔案編碼格式的一點探讨

很明顯是gb2312字元集。那怎樣看具體的編碼呢?有幾種方式:

關于檔案編碼格式的一點探讨

2、單擊emeditor菜單view→character code value,即可顯示,同時還顯示了unicode編碼,如下圖:

關于檔案編碼格式的一點探讨

3、用ultraedit打開,快捷鍵ctrl+h,即可顯示檔案内容編碼為:61 0d 0a ba ba,如下圖:

關于檔案編碼格式的一點探讨

同理,當我們将tmp-ansi.txt用記事本另存為其它格式時(當然用emeditor另存為的可選格式更多),也可用上述方式得到驗證其unicode編碼為:6c49,utf-8編碼為:e6 b1 89,如下圖驗證tmp-utf-8.txt(由記事本生成,預設帶有bom):

關于檔案編碼格式的一點探讨

即帶有簽名的bom:ef bb bf; 'a':61; crlf:0d 0a; '漢':e6 b1 89

再來看另存為unicode big endian的情況:

關于檔案編碼格式的一點探讨

果然位元組數多了!其bom:fe ff; 接下來的每個字元為2個位元組。當然若是小端的話,其bom就是ff fe了

linux平台測試示例

同理在linux測試也差不多,隻不過軟體沒了,用指令可看。建立檔案tmp-utf-8,同樣輸入:a+換行+漢,執行指令:

od -x tmp-utf-8

關于檔案編碼格式的一點探讨

将上述位元組每兩個反過來,即得:61 0a e6 b1 89 0a,不就是那utf-8編碼嗎,後面多了個換行 0a,不過我記得我沒有加第二個換行,不知道為什麼也有了

将上面檔案tmp-utf-8轉移到win7平台(友善起見,添加了擴充名.txt),分别用記事本和emeditor打開,如下圖:

關于檔案編碼格式的一點探讨

可見總共6個位元組,在記事本中隻有換行沒有回車,emeditor顯示為utf-8,無bom。另外可知此檔案并沒有bom,記事本為何還可以正确顯示?說明記事本很可能采取了上面所說的”猜“的方法,猜出用utf-8方式來顯示檔案而非gbk方式

我們再進一步測試:将上述win7的tmp-ansi.txt,tmp-utf-8.txt,tmp-unicode-be.txt轉移到linux下,觀察其反應,結果發現用vim打開tmp-ansi.txt時,'漢'為亂碼,vim解釋為latin1,且用:set fileencoding=utf-8之後,也并不能修複,拿到win7下同樣顯示不正常; 當用vim打開tmp-unicode-be.txt時,顯示正常,fileencoding為utf-16;

當打開tmp-utf-8.txt時,顯示正常,編碼為utf-8。下圖為顯示不正常時的情況:

關于檔案編碼格式的一點探讨

我想我那怪異的c++問題就是這樣造成的,導緻tinyxml解析混亂。

若此時我将tmp-ansi.txt再還原成latin1編碼,雖在linux上亂碼,但在win7上還是會被解釋成gb2312的。這也可了解,因為多位元組為單元的字元解釋成多個單一位元組,并不會造成位元組丢失,然後在合适的系統上就可正常顯示。然而,gb2312編碼并不與utf-8相容,是以哪怕轉為utf-8,也是亂碼。可見要想跨平台,還得兩邊都得用utf-8!

今天就總結這麼些,回頭有新的再補充。