BOM
自标记文件编码,UTF编码系列采用的是BOM技术,就是使用一个特殊字符(zero width no-break space),一个Unicode没用到的码位,其值为0XFEFF,放置在文件开头,当编辑器读的时候,看到这个BOM就知道文件采用的什么编码。es = 'A'
codes = ['utf-32','utf-16']
print([es.encode(code) for code in codes])
其输出[b'\xff\xfe\x00\x00A\x00\x00\x00', b'\xff\xfeA\x00']
,字节序列开头的\xff\xfe就是BOM,有时候他可能是\xfe\xff的形式,这说明文本是大字节序。0xFEFF的UTF-8编码是0x\ef0x\bb0x\bf。当我们使用UTF-16LE或者BE编码时,其输出不带BOM头。这是因为程序认为我们既然明确指定了带大小端的编码,说明我们知道并掌握自己怎么使用字节序列。Windows记事本中的BOM
BOM技术在Windows的记事本中比较常见,新建一个文本文件,写入相同内容,然后另存为记事本支持的四种格式:ANSI、Unicode、Unicode big endian、utf-8,查看其二进制形式,可见其BOM字段和相应编码的字节序列。Python打开文件时的'b'选项,使用二进制模式读取。# 文件内容都是:abAB巩★☆,但另存为时选择的编码格式不同。
fs = ['ansi.txt','unicode.txt','unicode big endian.txt','utf-8.txt']
for f in fs:
with open(f,'rb') as f_:
print(f_.readline())
b'abAB\xb9\xae\xa1\xef\xa1\xee'
b'\xff\xfea\x00b\x00A\x00B\x00\xe9\x5d\x05\x26\x06\x26'
b'\xfe\xff\x00a\x00b\x00A\x00B\x5d\xe9\x26\x05\x26\x06'
b'\xef\xbb\xbfabAB\xe5\xb7\xa9\xe2\x98\x85\xe2\x98\x86'
- Unicode.txt和unicode big endian.txt存储编码是UTF-16。更具体的,根据BOM信息,可知unicode.txt实际格式是UTF-16LE,而unicode big endian.txt实际格式是UTF-16BE。
其它编辑器的BOM
记事本使用BOM作文本编码信息的自标记,但是这并不是对编辑器的强制要求,也就是可带可不带,比如Linux下VIM编辑器,就不带BOM信息,这也是跨操作系统、编辑器编辑时,经常遇到问题的地方。vim设置中,查询BOM:set bomb?
,取消BOM::set nobomb
,设置BOM:set bomb
Windows的ANSI和代码页
至于ANSI格式为什么用GB系编码形式,需要多说一句。ANSI是美国国家标准协会,各国(非拉丁语系国家)指定自身文字编码,得到ANSI的认可之后使用。所以各个国家的ANSI实际指代编码各不相同,比如中国是GB2312,日本JIT,台湾地区Big5。ANSI实际是指向具体编码的“指针”。相较于,Windows记事本用ANSI来指代本地区的编码;Windows系统用代码页指代本系统编码,但这两个概念可视为一个概念。代码页或ANSI从命令行窗口输入chcp
可见。比如简体中文的操作系统会返回活动代码页;936
,这就是CP936,也就是GBK编码规范。代码页也是指向具体编码的“指针”。在记事本选择ANSI格式,实际是选择当前系统的活动代码页指代的具体编码,本例中选记事本ANSI,就是选代码页936,代码页CP936就指向GBK编码。在Unix系统和Windows系统间切换时,经常出现文本出现^M
和所有行挤成一行的问题。换行符问题
Window下,UE编辑器或者vim的十六进制打开文件时,可看到文件的最后有0x0d0a这两个字节。这分别是Windows中的换行(0x0a或\n)和回车(0x0d或\r)的ASCII码。VIM编辑器使用命令:% ! xxd
转换位十六进制显示;使用命令:% ! xxd -r
转换为文本显示。
这要从机械打字机说起,机械打字机打印完一整行之后,需要手动将“车”拉回到左侧,在回车的过程中,打字机自动完成了换行的操作,就可以继续打印第二行。- Windows系统借鉴这两个字符,使用回车和换行表示一行完成,另起一行。
- Unix系统打开Windows系统的文件时,由于多了回车符号,常看到行末尾出现奇怪的
^M
符号,这是Unix解析\r产生的。 - Windows系统里打开Unix系统文件时,由于少了回车符,所有行挤在一起,变成一行。
Python编程中编码问题
以上,我们理清了字符编码的发展脉络,知道了BOM以及不同操作系统、编辑器对BOM的应用。在Python编程中,我们如何处理编码问题呢?有以下几个注意事项:建议使用UTF-8
为了国际交流的便捷,Python使用UTF-8作为默认编码格式,在日常网页编程,数据库默认编码,以及文本存储时都建议使用UTF-8。decode和encode用法
什么时候encode,什么时候decode呢?需要记住一个原则:Python3字符串是Unicode码位,也就是说所有Python字面量实际是Unicode码位整数。所有转换都要以Unicode码位为中间体:str = 'abAB巩' #Unicode码位
gbk_seq = str.encode('gbk') #Unicode码位转GBK字节序列
str2 = gbk_seq.decode('gbk') #GBK字节序列转Unicode码位
utf_seq= str2.encode('utf-8') #Unicode码位转UTF-8字节序列
print(str,str2,gbk_seq,utf_seq)
Python2字面量实际是带有编码格式的字节序列。
三明治原则
三明治原则是在读写文件时,一定显式指定文件编码,确保Python程序中只处理Unicode码位,而不涉及字节序列。只有在输入和输出时,文件才转换为相应编码下的字节流。这种两端有具体编码,内里只有Unicode的处理方式,像两片面包夹着食材,所以称为三明治原则。with open('ansi.txt','r',encoding='gbk')as f,open('u8.txt','w',encoding='utf-8') as f2:
s = f.readline()
s = s[::-1]
f2.write(s)
以上代码,s字符串相关的处理都是Unicode码位。只有输入和输出时,才指定了相应的编码GBK和UTF-8。不要使用python2
不要使用python2!Python2中的Str类型,既是字符串,又是字节序列类型,特别复杂。非有特殊要求,建议不要使用。以下内容,足以一窥Python2的复杂:Python2中,s = 'abAB巩’
,是一个字节序列,它的具体值根据采用的编码(GBK,UTF-8)而不同。u = u'abAB巩'
表示的是Unicode码位。另外Python2中的编码类型声明# -*- coding:utf-8
声明的是程序中常量的编码类型,需要和文本保存编码类型一致。使用chardet推测字节序列的编码
若无法确定字节流的编码,模块chardet可用来推测字节流的编码。
import chardet
chardet.detect(b'\xef\xbb\xbfabAB\xe5\xb7\xa9\xe2\x98\x85\xe2\x98\x86')
返回是一个字典{'encoding':'utf-8',...}
,指出了最可能的编码及其可信度。总结
为了自标记文件编码,引入了BOM标签。我们分析了BOM在记事本中的表现,了解了BOM并不是强制要求。
了解了BOM、换行符导致的Linux和Windows之间的许多问题。
学习了Python中编程的三明治原则;读写文件时,显式指明文件编码。
本系列总结
从ASCII的简单,但不支持其他国家文字。
到扩展ASCII支持其他国文字,但产生乱码。
再到MBCS多字节编码可以支持汉语这种很多字符的语言,但由于太多语言编码导致各自为战互不兼容。
Unicode统一了世界各语言字符。Unicode几种编码形式中;
为了自标记文件编码,引入了BOM标签。我们分析了BOM在记事本中的表现,了解了BOM并不是强制要求。
了解了BOM、换行符导致的Linux和Windows之间的许多问题。
学习了Python中编程的三明治原则;读写文件时,显式指明文件编码。
人类交流因语言文字不同,造成很大困扰,无数IT从业人员也遇到各种乱码折磨,不认真攻克编码问题,他就会时不时跳出来。需要我们认清编码是发展和优化平衡的结果,包含了一系列优化、例外和兼容。本文代码
https://gitee.com/gongqingkui/codes/0qku9tc6az3j2dvoy5s8w39
作者:巩庆奎,大奎,对计算机、电子信息工程感兴趣。gongqingkui at 126.com
赞 赏 作 者
点击下方阅读原文加入社区会员