字符集和编码

字符集和编码--石锅拌饭

 今天被一个python编码问题折腾了半下午,编码问题一直是个让人比较纠结的问题,写这篇文章想简单的总结下python中一些常见的编码问题以及解决方案。这是第一篇,先总结下字符集和编码的一些基本概念和内容。


在编程中常常可以见到各种字符集和编码,包括ASCII,MBCS,Unicode等字符集。确切的说,其实字符集和编码是两个不同概念,只是有些地方有重合罢了。对于ASCII,MBCS等字符集,基本上一个字符集方案只采用一种编码方案,而对于Unicode,字符集和编码方案是明确区分的。那么先有几个术语需要说明下,下面这段术语说明摘抄自伯乐在线《关于字符编码,你所需要知道的知识》:

  • 字符集(Character Set):顾名思义,就是字符的集合。如ASCII字符集,定义了128个字符,而gb2312定义了7445个字符。计算机中字符集的严格定义来说指的是已编号的字符的有序集合(不一定连续)。
  • 字符码(Code Point):指的就是字符集中每个字符的数字编号。例如ASCII字符集用0-127这连续的128个数字分别表示128个字符;GBK字符集使用区位码的方式为每个字符编号,首先定义一个94X94的矩阵,行称为“区”,列称为“位”,然后将所有国标汉字放入矩阵当中,这样每个汉字就可以用唯一的“区位”码来标识了。例如“中”字被放到54区第48位,因此字符码就是5448。而Unicode中将字符集按照一定的类别划分到0~16这17个层面(Planes)中,每个层面中拥有216=65536个字符码,因此Unicode总共拥有的字符码,也即是Unicode的字符空间总共有17*65536=1114112。
  • 字符编码:将字符集中的字符码映射为字节流的一种具体实现方案。例如ASCII字符编码规定使用单字节中低位的7个比特去编码所有的字符。例如‘A’的编号是65,用单字节表示就是0×41,因此写入存储设备的时候就是b’01000001’。GBK编码则是将区位码(GBK的字符码)中的区码和位码的分别加上0xA0(160)的偏移(之所以要加上这样的偏移,主要是为了和ASCII码兼容),例如刚刚提到的“中”字,区位码是5448,十六进制是0×3630,区码和位码分别加上0xA0的偏移之后就得到0xD6D0,这就是“中”字的GBK编码结果。
  • 代码页(Code Page)一种字符编码具体形式。早期字符相对少,因此通常会使用类似表格的形式将字符直接映射为字节流,然后通过查表的方式来实现字符的编解码。现代操作系统沿用了这种方式。例如Windows使用936代码页、Mac系统使用EUC-CN代码页实现GBK字符集的编码,名字虽然不一样,但对于同一汉字的编码肯定是一样的。


1 ASCII

其中ASCII标准本身就规定了字符和字符编码方式,采用单字节编码,总共可以编码128个字符,如空格的编码是32,小写字母a是97,所以ASCII既是字符集又是编码方案。


2 MBCS

对于英文来说,128个符号编码已经够用了,然而对于其他语言比如中文,显然就不够了。因此就出现了多字节字符集MBCS(Multi-Byte Character Set)。如GB2312,GBK,GB18030,BIG5等编码都属于MBCS。由于MBCS大都使用2个字节编码,所以有时候也叫DBCS(Double-Byte Character Set)。我们在Linux系统中看到含有中文的文件编码常常是CP936,那这个其实就是GBK编码了,这个名字的由来是因为IBM曾经发明了一个Code Page的概念,把这些多字节编码收入其中,GBK编码正好位于936页,所以就简称CP936了。


3 Unicode

而后大家觉得各种编码太多不方便,不如所有语言字符都使用一套字符集来表示,于是就出现了Unicode。Unicode/UCS(Unicode Character Set)标准只是一个字符集标准,但是它并没有规定字符的存储和传输方式。Unicode是一种字符集而不是具体的编码,它主要有3种编码方式:最初Unicode标准使用2个字节表示一个字符,编码方案是UTF-16,还有使用4个字节表示一个字符的编码方案UTF-32。而后来使用英文字符的国家觉得不好,原来一个字符存储的现在变成了2个字符,空间增大了一倍,由此UTF-8编码。UTF-8编码中,英文占一个字节,中文占3个字节。


如上面所提到的,Unicode字符集主要采用UTF-8,UTF-16等方式进行编码存储,当然,gbk等字符编码也可以编码Unicode所有的字符集,也算是Unicode的一种字符编码。那么这样的话,计算机如何知道文件采用哪种方式编码呢?Unicode规范中又定义,在每个文件最前面加入一个表示编码顺序的字符BOM(Byte Order Mark)。比如石锅拌饭中的“石”的UTF-16编码是77F3,采用UTF-16方式存储使用2个字节,一个字节是77,一个字节是F3.存储的时候如果77在前面,F3在后面,则称为big endian方式。反之,则是Little endian方式。,这个字符正好也是2个字节,为FEFF。如果一个文本文件头两个字节威FEFF,则表示采用Big endian方式编码;否则就是Little endian方式。而UTF-8的BOM是EFBBBF,总结如下:

BOM_UTF8 '\xEF\xBB\xBF' 
BOM_UTF16_LE '\xFF\xFE' 
BOM_UTF16_BE '\xFE\xFF'


并不是所有的编辑器都会写入BOM,但即使没有BOM,Unicode还是可以读取的,只是需要指定编码,不然可能会失效。


4 ANSI

此外还有一种不得不提的是ANSI,ANSI在windows系统中极为常见,其实ANSI是Windows code pages,这个模式根据当前的locale选定具体编码,如果系统locale是简体中文则采用GBK编码,繁体中文为BIG5编码,日文则是JIS编码。


此外windows中喜欢把BOM_UTF16_LE编码称作Unicode,把BOM_UTF8称作UTF-8。也有人说UTF-8不需要BOM来标示,其实是不多的,这是因为编辑器一般默认使用UTF-8来测试字符编码而已,如果可以成功解码,就用UTF-8进行解码。即便最开始采用的是ANSI保存的,打开文件时还是最先使用UTF-8来解码。比如你用windows的记事本程序新建一个文件,写入“姹塧”并用ANSI编码保存,再次打开文件,会发现“姹塧”会变成“汉a”。


5 实例分析

还是以石锅拌饭的“石”字来看看在windows下面各种编码方式下的编码吧。打开windows的记事本程序,分别用ANSI,Unicode(实际是BOM_UTF16_LE),Unicode Big endian,UTF-8这几种编码方式看看最终是否跟之前分析的一样。这里使用UltraEdit来查看16进制编码,可以打开“编辑”-》16进制编辑功能来查看。


ANSI编码保存,编码是CA AF。这也表示GBK编码存储也采用了Big endian方式。

Unicode编码保存,编码是FF FE F3 77。 

Unicode Big endian编码保存,编码是 FE FF 77 F3。 

UTF-8编码保存,编码是EF BB BF E7 9F B3。


6 参考资料

发布了121 篇原创文章 · 获赞 133 · 访问量 68万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章