探索UTF-8中文编码的BOM标记问题
一直都被中文编码的问题困扰,因为在客户端存了中文的cookie,在服务器里问题编码出问题,今天又研究了一下这个问题。
我把一个含有中文的文本保存为utf-8格式的文件,文本的内容为:
abcdefgKeengle2009中文 |
之后用python来读取这里面的内容如下:
>>> f=open('1.txt','r') >>> s=f.read() >>> f.close() >>> s '\xef\xbb\xbfabcdefgKeengle2009\xe4\xb8\xad\xe6\x96\x87' >>> print s.decode('utf-8').encode('gbk') Traceback (most recent call last): File "", line 1, in UnicodeEncodeError: 'gbk' codec can't encode character u'\ufeff' in position 0: illegal multibyte sequence |
竟然发现在字符串开头多了三个字节(也就是\xef\xbb\xbf),就是这三个字节转为unicode后无法用
gbk编码。奇怪,怎么会在文件的开头多了三个字节呢,又开始了我的Google之旅。
找到了两篇文章,很有助于我的理解。
第一篇是详细介绍各种编码的:
5、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来标记文本文件的编码方式的。 详见:http://www.blogjava.net/dreamstone/archive/2006/11/28/83936.html |
找到的另一篇文章的作者也被BOM所困扰,其详细解释了出现问题的情形,但解决问题的办法似乎也是治标的了。
PHP在设计时就没有考虑BOM的问题,也就是说他不会忽略UTF-8编码的文件开头BOM的那三个字符。在在Bo-Blog的wiki看 到,同样使用PHP的Bo-Blog也一样受到BOM的困扰。其中有提到另一个麻烦:“受COOKIE送出机制的限制,在这些文件开头已经有BOM的文件 中,COOKIE无法送出(因为在COOKIE送出前PHP已经送出了文件头),所以登入和登出功能失效。一切依赖COOKIE、SESSION实现的功 能全部无效。”这个应该就是Wordpress后台出现空白页面的原因了,因为任何一个被执行的文件包含了BOM,这三个字符都将被送出,导致依赖 cookies和session的功能失效。 |
在我前面的试验中,要想成功的输出内容,就必须跳过前面的三个字节,或者跳第1个unicode字符:
>>> us=s.decode('utf-8') >>> print us[1:].encode('gbk') abcdefgKeengle2009中文 |
当然,也可以在notepad++里把文件的编码改为“UTF-8无BOM编码格式”,就不用作这个处理了。
但我们读取文件的时候都不知道这个utf-8文件是否有BOM标记,或者在读取客户端发来的Cookie的UTF-8的值是否有BOM标记,要怎么样处理才能得到优雅的代码呢?也许这会成为困扰我的下一个问题。
只能多注意了