天天看點

UTF8 + BOM産生問題與小結

寫python腳本的時候發現這樣一個問題:從xls檔案導出到txt時,無法直接轉換為int型資料,輸出檢視發現和檔案編碼方式産生的附加資訊有關用一個簡單的檔案舉例

90905

90907

90908

90909

90939

90940

90946

90959

90961

90965

當檔案分别用ascii,utf8,utf8+bom作為編碼格式時,顯示輸出結果如下:

使用ascii編碼的輸出:

['90905\r\n', '90907\r\n', '90908\r\n', '90909\r\n', '90939\r\n', '90940\r\n', '90946\r\n', '90959\r\n', '90961\r\n', '90965']

使用utf8編碼的輸出:

['90905\r\n', '90907\r\n', '90908\r\n', '90909\r\n', '90939\r\n', '90940\r\n', '90946\r\n', '90959\r\n', '90961\r\n', '90965']

使用bom編碼的輸出:

['\xef\xbb\xbf90905\r\n', '90907\r\n', '90908\r\n', '90909\r\n', '90939\r\n', '90940\r\n', '90946\r\n', '90959\r\n', '90961\r\n', '90965']

原來utf8+bom不能直接轉換int的原因在這裡,它在檔案頭插入了一個表示檔案編碼的資訊\xef\xbb\xbf,那麼UTF-8(無BOM)和UTF-8這兩個有什麼差別呢?BOM是什麼呢?

什麼是BOM?

BOM: Byte Order Mark

UTF-8 BOM又叫UTF-8 簽名,其實UTF-8 的BOM對UFT-8沒有作用,是為了支援UTF-16,UTF-32才加上的

BOM,BOM簽名的意思就是告訴編輯器目前檔案采用何種編碼,友善編輯器識别,但是BOM雖然在編輯器中不顯示,但是會産生輸出,就像多了一個空行。

Byte Order Marks are special characters at the beginning of a Unicode file to indicate whether it is big or little endian, in other words does the high or low order byte come first. These codes also tell whether the encoding is 8, 16 or 32 bit. You can recognise Unicode files by their starting byte order marks, and by the way Unicode-16 files are half zeroes and Unicode-32 files are three-quarters zeros. Unicode Endian Markers

Byte-order mark Description 

EF BB BF UTF-8 

FF FE UTF-16 aka UCS-2, little endian 

FE FF UTF-16 aka UCS-2, big endian 

00 00 FF FE UTF-32 aka UCS-4, little endian. 

00 00 FE FF UTF-32 aka UCS-4, big-endian.

UTF的位元組序和BOM

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編碼中有一個叫做"ZERO WIDTH NO-BREAK SPACE"的字元,它的編碼是FEFF。而FFFE在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來标記文本檔案的編碼方式的。

原來BOM是在檔案的開始加了幾個位元組作為标記。有了這個标記,一些協定和系統才能識别。

ok,說了這麼多背景,那麼如何解決這個問題呢?

如何使用BOM頭

BOM頭的删除

對UTF-16, Python将BOM解碼為空字串。然而對UTF-8, BOM被解碼為一個字元,如例:

>>> codecs.BOM_UTF16.decode( "utf16" )  u''  >>> codecs.BOM_UTF8.decode( "utf8" )  u'\ufeff'      

簡單的做法是在檔案讀入時使用

import codecs

f = codecs.open(sys.argv[1],'r', 'utf_8_sig')      

即可,具體可以參見[http://docs.python.org/library/codecs.html#module-encodings.utf_8_sig|http://docs.python.org/library/codecs.html#module-encodings.utf_8_sig]

或者:

u.lstrip( unicode( codecs.BOM_UTF8, "utf8" ) )      

BOM頭的添加

out = file( "someFile", "w" )
out.write( codecs.BOM_UTF8 )
out.write( unicodeString.encode( "utf-8" ) )
out.close()out = file( "someFile", "w" )out.write( codecs.BOM_UTF8 )out.write( unicodeString.encode( "utf-8" ) )out.close()      

詳細的過程解釋可以參見[http://mindprod.com/jgloss/encoding.html|http://mindprod.com/jgloss/encoding.html]

參考資料:

[http://blog.sina.com.cn/s/blog_3e9d2b350100as0b.html|http://blog.sina.com.cn/s/blog_3e9d2b350100as0b.html]

[http://4nail.iteye.com/blog/840612|http://4nail.iteye.com/blog/840612]