字符编码是计算机技术的基石,想要熟练使用计算机,就必须懂得一点字符编码的知识。
我们知道,计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit)有0
和1
两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从00000000
到11111111
。
上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为 ASCII 码,一直沿用至今。
ASCII 码一共规定了128个字符的编码,比如空格SPACE
是32(二进制00100000
),大写的字母A
是65(二进制01000001
)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的一位统一规定为0
。
英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用 ASCII 码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é
的编码为130(二进制10000010
)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。
但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é
,在希伯来语编码中却代表了字母Gimel
(ג
),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0--127表示的符号是一样的,不一样的只是128--255的这一段。
至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是 GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示 256 x 256 = 65536 个符号。
Unicode有许多种编码,比如说可以通过16个bit或者32个bit来把所有语言统一到一套编码里。举个栗子,字母A用ASCII编码的十进制为65,二进制为0100 0001;汉字“中”已经超出了ASCII编码的范围,用unicode编码是20013,二进制是01001110 00101101;A用unicode编码只需要前面补0,二进制是00000000 0100 0001。可以看出,unicode不仅解决了ASCII码本身的编码问题,还解决了超出ASCII编码范围之外的其他国家字符编码的统一问题。
虽然unicode编码能做到将不同国家的字符进行统一,使得乱码问题得以解决,但是如果内容全是英文unicode编码比ASCII编码需要多一倍的存储空间,同时如果传输需要多一倍的传输。当传输文件比较小的时候,内存资源和网络带宽尚能承受,当文件传输达到上TB的时候,如果 “硬”传,则需要消耗的资源就不可小觑了。为了解决这个问题,一种可变长的编码“utf-8”就应运而生了,把英文变长1个字节,汉字3个字节,特别生僻的变成4-6个字节,如果传输大量的英文,utf8的作用就很明显了。
不过正是因为utf-8编码的可变长,一会儿一个字符串是占用一个字节,一会儿一个字符串占用两个字节,还有的占用三个及以上的字节,导致在内存中或者程序中变得不好琢磨。unicode编码虽然占用内存空间,但是在编程过程中或者在内存处理的时候会比utf-8编码更为简单,因为它始终保持一样的长度,一样的长度对于内存和代码来说,它的处理就会变得更加简单。所以utf-8编码在做网络传输和文件保存的时候,将unicode编码转换成utf-8编码,才能更好的发挥其作用;当从文件中读取数据到内存中的时候,将utf-8编码转换为unicode编码,亦为良策。
如上图所示,当需要在内存中读取文件的时候,此时将utf-8编码的内存转换为unicode编码,在内存中进行统一处理;当需要保存文件的时候,出于空间和传输效率的考虑,此时将unicode编码转换为utf-8编码。在Python中进行读取和保存文件的时候,必须要显示的指定文件编码,其余的事情就交给Python的相关库去处理就可以了。