七月 22nd, 2009
一直都被中文编码的问题困扰,因为在客户端存了中文的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来标记文本文件的编码方式的。
详见:
找到的另一篇文章的作者也被BOM所困扰,其详细解释了出现问题的情形,但解决问题的办法似乎也是治标的了。
PHP在设计时就没有考虑BOM的问题,也就是说他不会忽略UTF-8编码的文件开头BOM的那三个字符。在在Bo-Blog的看 到,同样使用PHP的Bo-Blog也一样受到BOM的困扰。其中有提到另一个麻烦:“受COOKIE送出机制的限制,在这些文件开头已经有BOM的文件 中,COOKIE无法送出(因为在COOKIE送出前PHP已经送出了文件头),所以登入和登出功能失效。一切依赖COOKIE、SESSION实现的功 能全部无效。”这个应该就是Wordpress后台出现空白页面的原因了,因为任何一个被执行的文件包含了BOM,这三个字符都将被送出,导致依赖 cookies和session的功能失效。解决的办法嘛,如果只包含英文字符(或者说ASCII编码内的字符),就把文件存成ASCII码方式吧。用UE等编辑器的话,点文件->转换 ->UTF-8转ASCII,或者在另存为里选择ASCII编码。如果是DOS格式的行尾符,可以用记事本打开,点另存为,选ASCII编码。如果 包含中文字符的话,可以用UE的另存为功能,选择“UTF-8 无BOM”即可。”
详见:
在我前面的试验中,要想成功的输出内容,就必须跳过前面的三个字节,或者跳第1个unicode字符:
>>> us=s.decode('utf-8')
>>> print us[1:].encode('gbk')
abcdefgKeengle2009中文
当然,也可以在notepad++里把文件的编码改为“UTF-8无BOM编码格式”,就不用作这个处理了。
但我们读取文件的时候都不知道这个utf-8文件是否有BOM标记,或者在读取客户端发来的Cookie的UTF-8的值是否有BOM标记,要怎么样处理才能得到优雅的代码呢?也许这会成为困扰我的下一个问题。