一、编码系统的出现和发展
在PC刚开始出现时,只有ASCII一种编码系统,因为这种编码系统只包括大小写的英文字母、数字、控制字符等127个字符,所以对英语用户是友好的。
随着PC在全球的日益普及,各个国家也需要对本国的语言字符进行编码,以方便对包含本国语言的信息进行处理。这其中,大陆出现了gb2312等编码系统,台湾,韩国,日本也出现了自己的编码系统。这些编码系统出现的时间比ASCII晚,为了兼容ASCII码,这些编码系统都在ASCII码基础上做了扩展,但是每个编码系统都有自己的数字和字符的映射方式,造成了这些编码系统之间的不兼容性,因此以某种编码系统创建的字符串,如果用另一种编码系统进行解码的话,就会出现乱码。
为了解决编码系统混乱的局面,Unicode编码系统出现了,它将世界上所有的语言字符和符号都进行统一的编码。既然大家都采用同一种编码系统,自然也就不会混乱了。
二、字节串和字符串的区别:
字节串,顾名思义,就是一个字节的序列,这也是PC中数据的最终格式(传输或存储)。字符在计算机中只是一种抽象,字符串是这种抽象的序列。
例如,“你好”这两个汉字,呈现在你面前的就是2个字符长度的字符串,如果采用gb2312编码系统进行保存的话,则是一个4个字节长度的字节串,而采用utf-8编码进行保存,就是一个6个字节长度的字节串。刚开始了解字符编码的概念时,可能会将字节串和字符串混为一谈,明确分清这两个概念后,会对后面的编码系统有更好的理解。
三、介绍几种常见的编码
ASCII编码系统,
ASCII是American Standard Code for Information Interchange的缩写,每个ASCII码以1个字节(Byte)储存,从0到数字127代表不同的常用符号,例如大写A的ASCII码是65,小写a则是97。由于ASCII只占用一个字节的低七个位,最高位并不使用,所以最高位保持为0。
gb2312, gbk:
GB2312(1980年)一共收录了7445个字符,由于支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号。2000年的 GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。
从ASCII、GB2312、GBK 到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。ascii字符还是以一个字节存储,而非ascii字符(如汉字)使用2个字节存储。区分中文编码的方法是高字节的最高位为1。GB2312、GBK和GB18030都属于双字节字符集 (DBCS)。在读取DBCS字节流时,只要遇到高位为1的字节,就可以将该字节和下一个字节作为一个双字节编码,而不用管低字节的高位是什么。
Unicode:
在网上关于Unicode的资料有很多,比如http://zh.wikipedia.org/wiki/Unicode,就不过多描述了。
四、Unicode和其他编码系统的区别
概念上,Unicode编码系统可分为编码方式和实现方式两个层次。目前实际应用的编码方式是版本UCS-2,即采用16位的编码空间,也就是每个字符占用2个字节;而实现方式采用的是utf-8等编码。其他编码系统如ASCII,gbk等并不区分编码方式和实现方式。
为什么会出现这种差别?
我认为2个方面的原因导致了这种差别:
1 占用空间的大小 如果Unicode和其他编码系统一样,不区分编码方式和实现方式,统一用2个字节表示和保存信息,那么存储和传输全部由ASCII字符组成的信息,将要比其他编码系统浪费一倍的空间和时间。如果采用4个字节表示一个字符的UCS-4标准,浪费的空间会更大。而在这方面,其他编码系统没有这种缺点,因为这些编码系统采用中英文共存的方式进行编码,在这些编码系统中,ASCII字符还是占用一个字节,而非ASCII字符占用2个字节。
2 编码方式的识别 目前的xml,html和其他一些文档都会在文件的开头用ASCII字符标识出编码方式,因此软件在识别此类文件时,会先读取文件头的部分数据(字节串),以ASCII编码方式进行解码(转成字符串,因为ASCII编码中字节串和字符串完全相同,所以这部分数据可以直接当成字符进行处理),进而找到文档的编码方式,最后以找到的编码方式再对数据进行完整的解码操作。如果按照2个字节代表一个字符存储,这种自标识编码系统的方式就无法使用了。
基于以上2个原因,Unicode编码系统分为2个层次,在内存操作字符串时,使用2个字节表达一个字符的编码方式,但是在存储和传输时,使用utf-8等实现方式的编码。utf-8编码对ASCII字符的编码和其他编码系统一样,只占用一个字节,而对于非ASCII字符则需要多个(>=2)字节。
我们再往深处想一想,按照Unicode编码系统的2个层次的划分,不但utf-8编码可以作为Unicode字符的存储编码方案,其它如gbk,big5等编码都可以用做Unicode字符的存储编码方案,只不过使用这些编码系统保存Unicode字符时,只能保存该编码系统支持的字符集和Unicode字符集的交集部分,不像utf-8存储编码方案那样,和Unicode字符有直接的映射关系,可以保存所有的Unicode字符。
但是utf-8编码方案也有自己的缺点,虽然在保存ASCII字符方面和其他编码系统在占用空间上一样,但是对于非ASCII字符,却比其他编码方案要多,例如对简体汉字的“汉”字,gbk只占用2个字节,utf-8需要使用3个字节来保存。
因此目前在Windows系统内部使用Unicode编码处理字符串,但是在存储上还是采用默认的本地编码方式。比如我使用的WindowsXP简体中文版,记事本,word等程序默认使用gb2312编码。而常用的notepad++编辑器,默认的也是ansi(即gb2312编码),但是也提供了utf-8格式用于保存文本。
五、数据编码方式的确定
当用户接收到代表信息的数据(字节串)时,如何确定数据的编码方式呢?
当前了解到的有3种方式:
- 检测,依照某个算法对字节串检索,当其符合某个编码系统的特征时,就以该编码系统对字节串解码,但这种方式的准确率不高,经常会出现乱码的现象;
- 指定,其中又细分为2类:一类是上面描述过的通过在文件开始处以ASCII码指定编码方式,另一类是用户了解数据的编码方式,由用户指定应该以何种编码系统对系统进行解码,常见的例子有:浏览器中用户可以指定网页的解码方式,一些编辑器如notepad++也可以让用户指定文件内容的编码方式;
六、python处理字符的方式
python对字符串的处理分为三个阶段:
- 在python2.2之前,没有对unicode的支持,只有str数据类型;
- 从2.2版开始,添加了unicode数据类型,str和unicode数据类型共存;
- 在python重大的改进版本3.x中,str回归了该数据类型的本意,只表示字符串,和2.x版本中的unicode数据类型等同;而字节串由两种数据类型表示,bytes表示不可改变的字节串,bytearray表示可以改变内容的字节串;
http://www.woodpecker.org.cn/diveintopython3/strings.html 中详细解释了python3中对字符处理的细节,也包括对编码系统原理和演化过程极为精彩的描述,这里就不详述了。因为python2.x版本还在大量应用中,因此这里着重讲解python2.x对字符串的处理方式。
python2.x中常用的字符串类型有两种,一种是str,另一种是unicode。实际上str担当着2个数据类型的作用:字节串和ascii字符串。想一想从文件读出的数据用什么类型表示,你就能理解我的意思了。如果只是处理ASCII码的字符串操作,str足矣;但是一旦碰上非ASCII字符串,str就无能为力了。
举个例子来说明这个问题:在字符串“我 love 中文”(采用gb2312编码12bytes,utf-8编码为15bytes)中查找字符串“ov”,这相当于在一个字节串中查找一个2bytes序列,其中第一个字节值为82,第二个字节值为89,即使这个字节串中包含非ASCII字符,因为采用值相等的查找方式,str.find()函数是可以胜任的。
但如果是str.find("中文"),可就麻烦了,如果源字符串和查找子串采用相同的编码方式,str.find()还可以正常工作,但如果采用了不同的编码方式,就得不到正确的结果了,而且即使采用同一种编码方式,找到了匹配的子串开始位置,但这个位置是以字节作为单位的,并不是以字符为单位的,如果你想作进一步的处理,还要参考这个字符串的编码方式才行。
所以在python2.x中,使用unicode数据类型才是处理字符串的正确方式,在处理任何字符串之前,先将输入的字节串(str)按照指定的编码方式转换成字符串(unicode),然后进行处理,处理的结果按照你想要的编码方式再转换成字节串(str)以保存或传输。
简单来说,在python2.x中字符串的处理方式就是:解码(decode)->处理->编码(encode),解码按照指定的编码方式将字节流转换成字符流,编码按照指定的编码方式将字符流转换成字节流。python提供了encode和decode两个函数用于字节串和字符串间的转换。
七、几个涉及字符编码的典型场景
1 python源代码
如果不指定python代码的编码方式,python2.x默认是按照ascii来解释python源文件的,如果源代码中包括非ascii字符,需要在文件开始处指出python的编码方式。唯一的例外是如果python代码是以utf-8等编码方式保存的,也可以不声明编码方式,具体原因和实现细节有关。
python编码方式的声明和以utf-8编码保存而无需指定编码方式的细节可以参看PEP-0263:http://www.python.org/dev/peps/pep-0263/
2 输入输出
来自于console,文件,网络的输入都是基于字节的,在进行字符串处理之前,需要按照指定的编码方式进行解码。比如,来自新浪的一个网页:http://news.sina.com.cn 在这个web page开头声明了编码方式为gb2312,再继续处理之前,先使用decode函数解码为unicode字符串:web_page.decode('gb2312')。
当经过处理的字符串需要保存时,需要按照指定的编码方式进行编码,再保存到文件中,如将一个xml文档转换为文件数据保存:fd.write(unicode_xml.encode('utf-8'))