字符编码
字符的编码一直是一个令人头疼的问题,如果没有完全理解它,那么总有一天会踩进字符编码的坑里的。最近正好有空,梳理了编码相关的知识,来一篇博客,整理相关的细节。
ASCII
从计算机发明的时候起,字符的编码就是一个令人困扰的问题。众所周知,计算机只能存储二进制的数据,但是现实生活中的数据都不是二进制,比如我想在计算机中存储一个’hello world’的字符串,那么如何存储呢,肯定是为每个字符们约定一个对应的二进制数喽,这样计算机才能存储。当需要读取时,再利用二进制数找到对应的字符就可以实现字符的存储啦。
这个字符与二进制数的对应关系就是ASCII码表,当需要存储字符’h’时,计算机实际存储ASCII码表对应于’h’的01101000,当读取到01101000的二进制串时,也会查询ASCII码表,这样就知道存储的是’h’啦。ASCII码表对所有的字母,数字以及符号进行了编码,这样在使用英文语言的国家中,就可以存储所有的内容啦。ASICC码表的对应关系可以参考此网站
GB2312和GBK
使用ASCII码,英文字符存储的问题就已经解决了,但是中文的存储的问题依旧解决不了。因为中文主要不是由几个字母组成的,而是由成千上万个汉字组成的。在ASCII设计中,只使用1字节的数据就可以存储英文语系所有的基本内容了,但是中文的存储就没这么简单了。因此,中国人字节创造了一套中文的编码规则,利用2字节的二进制数表示一个特定的中文。首先创造了GB2312编码,收录了6763个汉字,后来又创造了GBK编码,收录27484个汉字。同时GBK编码还兼容ASCII编码,使用1字节表示英文,2字节表示中文。
Unicode
在Unicode没有出现之前,很多国家都有自己语言的编码规则,中国有自己的一套,日本有一套日本的,韩国对韩语也设计了一套编码规则,彼此互不相同。在这种背景下,国际联盟提出一套统一的编码表,可以将全世界所有语言都进行编码,这个统一的编码就是Unicode。Unicode采用2~4个字节存储一个字符,可以存储世界上所有语言中的字符。
UTF
Unicode虽然解决了不同语言编码不统一的问题,但是带来一个新的问题就是单个字符占用的空间太大了。比如一个普通的字母,原来采用ASCII编码时,只需要占用1字节的存储空间,现在却需要2字节,存储空间占用多了1倍。为了解决这个问题,UTF(Unicode Transformation Format)应运而生。
在Unicode需要进行传输或存储时,可以将Unicode编码转换为UTF编码,UTF采用1~4字节进行存储的编码方案,对于原来ASCII中的字符,只需要1字节就可以存储了,对于欧洲语系中的字符,采用2字节进行编码,对于东亚语系中的字符,采用3字节进行编码,其他则采用4字节进行编码。这样编码后,占用的空间就能得到缩减。
Python2字符编码
python2默认编码
在python2的代码中,经常会在代码的首行加上”#coding utf8”,这段注释存在的意义是什么呢
原因在于python2的默认编码方式是ASCII,而由于前面的理论,ASCII是不支持中文的,因此如果不加上前面的注释,当在代码中出现任意的中文字符时,就会报错。错误信息如下: “Non-ASCII character ‘\xe6’, but no encoding declared”
string
在python2中string类型看起来是可以直接操作中文等非ASCII的字符的,比如我们可以指定 s = “中文”,然后通过print s,也是可以正常工作的。但是我们查看字符串s的长度就会发现特别诡异的现象,事实上len(s)为6,为什么会出现这种情况呢?而且我们之前说中文存储时是可以采用不同方式编码的,这个字符串是怎么编码的呢?
事实上,python2中string类型事实上并不是字符串,真正精确的描述应该是字节串。string类型事实上就是存储了数据对应的二进制数据。这个可以通过直接输出s,可以看到显示的数据是’\xe4\xb8\xad\xe6\x96\x87’,这个是因为我们配置的中文的默认编码是utf-8,因此对于需要的存储的数据”中文”,string中事实上存储的是这两个汉字对应的的utf-8二进制编码。而中文在utf-8编码中占用3个字节,因此最终的len(s)就是6。从这里也印证了,string并不是字符串,只是存储的是二进制字节串。
那么string对应的数据都是采用什么样是编码方式呢?答案是可以采用任意的编码方式,string中的数据可能是utf-8编码的,也可能是gbk方式编码的。因为string真正关心的是字节,真正存储的是二进制串,因此任意的编码方式,最终都是可以对应于特定的二进制串的,都是可以采用string存储的。
unicode
python2中由于string类型是字节串,为了进行字符的存储,又增加了一种类型,叫unicode。他就是将数据采用unicode方式进行编码存储的。这个才是我们一般意义上理解的字符串。
encode和decode
在python2中,string是可以采用任意编码的字节串,而unicode是采用unicode编码的串,他们可能表示的都是同样的内容,但是对应的编码方式不同,因此最终存储的二进制串就不同了。而这两种类型事实上是可以互相转换的,因为对于特定的string类型,只要知道了编码方式,是可以知道表示的内容的,因此也是很容易就知道对应的unicode编码的。反过来,知道了unicode存储的串,是可以知道表示的内容的,如果想要进行特定类型的编码,也是很容易做到。
python2提供了decode方法,可以将string类型转换为unicode类型,即s.decode(encoding),即可转换为unicode类型
同样python2提供了encode方法,将unicode类型转换为string类型,即u.encode(encoding),即可转换为string类型
文件编码
根据我们前面介绍的理论,存储的文件真正存储的是二进制串,我们希望存储特定的内容,事实上,我们是将这些内容按照特定的方式进行编码,然后存储编码后对应的二进制串。在读取文件时,我们也需要知道编码的方式,然后按照之前的编码格式进行解码,即可将二进制串转换为特定的内容,从而供我们展示。生活中,我们时不时会遇到中文乱码的问题,这个问题出现的主要原因在于,读取文件时采用的解码方式和编码方式不一致,因此将二进制串解码成了错误的内容,从而出现了乱码。这种情况下,我们就应该尝试采用其他的解码方式进行文件读取,才能正确读取文件内容。