字符编码
字符编码的问题一直搞得自己云山雾绕的,这次特地记录一下。
首先一些特别大白的铺垫就不多说了,如果对这个字符编码一点概念都没有,建议可以查查资料或者去廖大的官网看下,大概知道这个是干嘛使的。
1、基本概念
讨论编码问题之前,需要先知道2个概念:
1.1、字符集
说白了就是某些特定字符的集合,我们常见的字符集有 ASCII、GB2312、GBK、Unicode、utf-8等,而 Unicode 字符集是可以包含所有国家文明中的所有字符的。
1.2、字符编码
所有的文件在计算机中最终是以二进制序列来保存的,不同的序列就可以表示不同的内容。字符编码的目的就是对不同的字符编码设计合理的唯一二进制序列在计算机中进行存储表示。
2、字符编码简单了解
任何事物刚开始都会有或多或少问题,然后慢慢趋于成熟,字符编码也是如此。
2.1、ASCII编码
计算机是美国人发明的,因此,最早只有127个字符被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码。
2.2、ISO-8859-1编码
计算机到了欧洲发现很多欧洲国家符号无法表示,为了表示更多的欧洲等国家使用的字符,对原始的 ASCII 编码范围进行了扩充,这就是ISO-8859-1编码。
ISO-8859-1编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。
因为ISO-8859-1编码范围使用了单字节内的所有空间(即8位,0-255),在支持ISO-8859-1的系统中传输和存储其他任何编码的字节流都不会被抛弃。换言之,把其他任何编码的字节流当作ISO-8859-1编码看待都没有问题。这是个很重要的特性,MySQL数据库默认编码是Latin1(Latin1是ISO-8859-1的别名,有些环境下写作Latin-1)就是利用了这个特性。ASCII编码是一个7位的容器,ISO-8859-1编码是一个8位的容器。
2.3、GB2312、GBK、GB18030 编码
为了解决汉字在计算机中的编码问题,最开始推行的方案是采用两个字节对常用的 6763 个汉字和其它一些符号进行编码。同时为了保证对 ASCII 编码的兼容性,每个字节的最高一位比特总是为 1。这样计算机遇到高位为1的字节就会采用汉字编码方案,遇到高位为0的字节采用 ASCII 编码方案。这种解决方法我们就可以称为 GB2312 编码方案。
虽然 GB2312 编码能够覆盖99.75%使用频率的汉字,毕竟还是有无法编码的字存在。GBK 的出现弥补了少数汉字无法进行编码解析的问题,它是 GB2312 编码的扩展,向下兼容 GB2312,同时包含了繁体字。
GB18030 进一步扩展了 GBK 所包含的字符集范围,囊括了中国少数民族所用的字符等。同时也是向下兼容 GBK、GB2312的。
2.4、BIG5、Shift_JIS、EUC-KR 编码
与 GB2312 等编码标准的出现相似,BIG5编码 主要是台湾地区为了解决对繁体字的处理。Shift_JIS 为日本电脑系统常用的编码方案,EUC-KR 为韩国电脑系统常用编码方案。
3、Unicode 字符集& UTF-8 编码
3.1、Unicode 字符集
你可以想得到的是,全世界有上百种语言,各国有各国的标准,同一个编码值在不同的字符集可能代表着不同的字,就会不可避免地出现冲突,结果就是,在多语言混合的文本中,显示出来会有乱码。
因此,Unicode应运而生。Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。
Unicode 现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,“汉”这个字的Unicode编码是U+6C49。
需要注意的是,Unicode只是一个字符集,它只规定了符号与二进制代码之间的对应关系,却没有规定这个二进制代码应该如何存储。
比如,汉字"严"的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。
这里就有两个严重的问题,第一个问题是,如何才能区别 Unicode 和 ASCII ?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果 Unicode 统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。
3.2、UTF-8 编码
UTF-8 是 Unicode 的实现方式之一。
UTF-8(UCSTransformation Format 8bit)就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示)等,不过在互联网上用的很少。
UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有二条:
- 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
- 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 unicode 码。
Unicode 符号范围(十六进制) | UTF-8 编码方式(二进制) |
0000 0000-0000 007F | 0xxxxxxx |
0000 0080-0000 07FF | 110xxxxx 10xxxxxx |
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
跟据上表,解读 UTF-8 编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。
UTF-8编码有一个额外的好处,就是ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。
搞清楚了ASCII、Unicode和UTF-8的关系,我们就可以总结一下现在计算机系统通用的字符编码工作方式:
在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。
4、编码与解码
4.1、解码
一串二进制数,使用一种编码方式,转换成字符,这个过程我们称之为解码(python中解码用decode())。就像解开密码一样,程序员可以选用任意的编码方式进行解码,但往往只有一种编码方式可以解开密码显示出正确的文字,而使用错误的编码方式,产生其他不合理的字符,这就是我们通常说的————乱码!
4.2、编码
一串已经解码后的字符,我们也可以选用任意类型的编码方式重新转换成一串二进制数,这个过程就是编码(python中编码用encode()),我们也可以称之为加密过程,无论使用哪一种编码方式进行编码,最终都是产生计算机可识别的二进制数。