ASCII,Unicode,GBK和UTF-8字符编码的区别和联系

如果经常写python2,肯定会遇到各种“奇怪”的字符编码问题,每次都通过谷歌解决了,但是为什么会造成这种乱码、decode/encode失败等等,本文就字符和字符编码做一个总结,更加清晰区分诸多的编码。

字符集

一个系统支持的所有抽象字符的集合。字符是文字和符号的总称,包含各个国家文字、标点符号、图像符号、数字等。它为每一个字符分配一个唯一的ID,一般称之为码位、码点。

字符编码

它是一套规则,使用该规则能够将自然语言的字符的一个集合与其他东西的一个集合进行配对,在符号集合和数字系统中建立映射联系。在计算机中,处理信息是利用元件不同状态组合来存储和处理信息的,因此,字符编码就是将符号转化为计算机可以接受的数字系统的数,称为数字代码。它将上面字符集的码位转化为字节序列的规则,此过程称之为编码、解码。

常见字符集

ASCII字符集,GB2312字符集、Unicode字符集等。计算机需要准确处理各种字符集文字,需要进行字符编码,以便能够识别和存储各种文字。

常见字符编码

UTF编码

ASCII码

在计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit),有0和1两种状态,因此,8个二进制位可以组合出256种状态,这被称为字节(byte)。上个世纪60年代,美国制定了一套字符编码,对英文字符与二进制之间做了联系,这被称为ASCII码,一直沿用至今。

ASCII码一共规定了128个字符,比如SPACE是32,A是65,这128个符号只咱用了一个字节的后面七位,最前面的一位统一规定为0。

非ASCII码

英语用128个符号编码足够了,但是用来表示其他语言显然是不够的,于是,欧洲有些国家利用字节中闲置的最高位编入了新的符号,这样一来,欧洲国家使用的编码体系,可以最多表示256个字符。

但是到了亚洲国家,使用的符号更多了,光汉字就10万多个,一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如常见的GB2312编码,使用两个字节表示一个汉字,所以理论上最多可以表示256*256=65536个符号。

GBK码

GBK编码是对GB2312的扩展,完全兼容GB2312。采用双字节编码方案,剔出xx7F码位,共23940个码位,共收录汉字和图形符号21886个,GBK编码方案于1995年12月15日发布。它几乎完美支持汉字,因此经常会遇见GBK与Unicode的转换。

Unicode码

如上文所述,世界上有多种编码方法,同一个二进制数字可以被解释称不同的符号。因此,在打开一个文本文件时候,就必须知道它的编码方式,用错误的编码方式打开,就会出现乱码。

假如,有一种编码,将世界上所有的符号都纳入其中,每一种符号都给予独一无二的编码,那么乱码问题就不会存在了。因此,产生了Unicode编码,这是一种所有符号的编码。

Unicode显然是一个巨大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如U+0041表示英语的大写字母A,U+4e25表示汉字严。

在Unicode庞大的字符集的优势下,还存在一个问题,比如一个汉字,“严”的Unicode是十六进制4e25,转成二进制足足有15位,也就是,这个符号需要2个字节,表示其他字符还存在3个字节或者更多。计算机怎么区别三个字节表示的是同一个符号而不是分开表示三个呢?如果Unicode统一规定,每个符号用3个字节表示,但是某些字母显然不需要3个,那么就浪费了空间,文本文件大小超出了很多,这显然是不合理的。直到UTF8字符编码出现了。

UTF8字符编码

随着互联网的发展,强烈要求出现一种统一的编码方式。UTF8就是在互联网中使用最多的对Unicode的实现方式。还有其他实现,比如UTF16(字符用2个字节或者4个字节表示),UTF32(字符用4个字节表示)。UTF8是Unicode的实现方式之一,也是最为常见的实现方式。

UTF8的最大特点是,它是一种变长编码,可以使用1-4个字节表示一个符号,根据不同的符号来变化字节长度。

UTF8编码规则只有两条:

1)对于单字节的符号,字节的第一位设为0,后面的7位为这个符号的Unicode码。因此,对于英文字母,UTF8编码和ASCII编码是相同的。

2)对于非单字节(假设字节长度为N)的符号,第一个字节的前N位都设为1,第N+1设为0,后面字节的前两位一律设为10,剩下的没有提及的二进制,全部为这个符号的Unicode码。

下面总结下编码规则:

Unicode符号范围     |        UTF-8编码方式
(十六进制) | (二进制)
----------------------+---------------------------------------------
- 007F | 0xxxxxxx
- 07FF | 110xxxxx 10xxxxxx
- FFFF | 1110xxxx 10xxxxxx 10xxxxxx
- FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

举个例子,以汉字“严”为例,它的Unicode是4e25(100111000100101),对应上表,处于第三行,因此严的UTF8编码需要3个字节。然后,从严的最后一个二进制位开始,从后向前填入X,多出的位补0,就可以计算出 11100100 10111000 10100101,转成十六进制就是E4B8A5。

Unicode与UTF8之间转换

严的Unicode编码是4e25,UTF8是E4B8A5,两者是不一样的,可以通过程序实现转码。

在Windows下有记事本小程序notepad.exe,打开文件后可以通过另存为,选择编码格式,重新保存新的文本文件。支持ANSI,Unicode,Unicde big endlian和UTF8。

1)ANSI是记事本默认编码方式,对于简体中文是GB2312。正是因为这个,Python读取文本文件时候一定要小心它的编码类型,因为不能直观到文本文件的格式。

2)Unicode编码这里使用UCS-2编码方式,采用两个字节存入字符的Unicode编码。

Python编码为什么如此蛋疼

总结了几种编码和编码规则,但是在Python2中编码问题就像幽灵一样困扰着开发者,其根本解决办法是理解清楚python2在内存中的字符存在的编码格式,在程序代码中始终采用Unicode编码处理,只有在输出时候才做encode处理。核心思想是:保证Python运行过程中字符编码格式是unicode编码,在任何地方。关于Python乱码问题,会在专门文章做分析,这里提供一个链接供参考。

Python编码为什么那么蛋疼?

编码探测

使用 chardet 可以很方便的实现字符串/文件的编码检测。尤其是中文网页,有的页面使用GBK/GB2312,有的使用UTF8,使用chardet基本可以探测出编码格式。

延伸阅读

谈谈Unicode编码

上一篇:Java中Collection 的基本认识


下一篇:CSS中的背景、雪碧图、超链接的伪类样式