字符编码和字符集知识点总结最终篇

/ 默认分类 / 0 条评论 / 610浏览

一.编码

编码是将信息从一种格式转换为另一种格式的过程,这里我们强调的是过程,所以编码实际上可以认为是两种数据格式之间的映射关系.

实际上,在计算机还没有出现的时候,编码就已经存在了,比如摩尔斯电码: 摩尔斯电码,使用点和划的组合来表示字母,如下图所示:

所以,摩尔斯电码中约定的点划组合和字母之间的映射关系就是编码.这个编码我们叫做摩尔斯编码.如果现在写入数据的一方,依据摩尔斯编码进行数据写入,那么:

--- -.-

就表示ok,那么读取这个数据的一方,也需要依据摩尔斯编码进行读取,首先他发现第一个字母是---,依据摩尔斯编码,这个数据代表字母o,然后继续读取发现空格,表示这是一个字母的终止标志,于是读取到下一个字母,发现先一个数据代表字母k,读取完毕后得到的含义就是ok.

以上过程就是一个简单的依据某种编码方式进行数据写入和读取的过程.计算机世界也是如此.

二.从摩尔斯电码到计算机编码

详细了解过摩尔斯电码的童鞋一定知道,摩尔斯电码也可以认为是一种二进制数据编码,其中点可以看做0,划可以看做1.

从上面的图,我们可以清楚的看出摩尔斯电码的拓展过程,可以总结为: 支持的编码数量=2^点划的数量 所以,摩尔斯码也被称为二进制码,因为这种编码的组成元素只有点和划两个,和二进制01相同.

上面讨论的摩尔斯编码,实际上只是为了引出我们最终要说的计算机中的编码,我们可以认为,摩尔斯编码是人类世界在电报机时代使用的.

保持电键的按下状态一小段时间,就会产生一个 “点” 的莫尔斯码。按下状态保 持的时间更长一些就会产生一个 “划” 的莫尔斯码。当电报机的电键被按下时,发声器中的电磁铁拉动上面的活动横杠下降,它会发出 “滴”的声音。当松开电键的时候,横杠弹回到原来的位置,发出“嗒〞 的声音。一次快速的“滴-嗒〞声代表点;一次慢速的“滴 嗒”声则代表划。当然也可以在接收端控制一只笔,来动态地写出点和划.然后根据摩尔斯编码表就可以解析出实际的内容了.

可以认为计算机其实也是一样的只能表示0和1两种数据,因此,计算机同样需要一套编码体系,来基于0和1的组合来表示人类需要的数据.

三.ASCII编码的诞生

在计算机诞生初期,美国人基于一套名为 ASCII的编码方式来利用计算机进行数据读写

现在我们需要明确几个概念了:

所以说,字符集和字符集中字符对应的相应编码的二进制码点是定死的,但是存储在计算机中的方式,是由字符编码方式来定的,下面我们介绍完utf-8和utf-32之后就可以更好的理解这句话了.

ascii编码的每个码点占空间位1个字节(byte),也就是8位(bit),字节刚好是计算机存储数据的最小单位,所以使用ascii编码的数据占用的存储空间相对较小.但实际上,ascii编码中最多只利用了7位数据,1字节的最高位被统一保留为0,所以总共可以表示2^7中不同的组合,所以也就可以表示128中字符,对应的映射关系参考下面的表格.

ascii字符集的标准数据表参考点击查看

所以,当我们在计算机中创建一个文本文件,并输入ok,如果我们想保存这个数据到计算机中,那么计算机就需要利用ascii编码将字符数据转为二进制数据然后再进行存储.所以实际存在在磁盘上的数据是0100111101101011(再底层,比如数据存储在固态硬盘中,那么0和1实际就是浮栅晶体管中浮栅极中的电子数量)

四.其他类型的编码

上面我们介绍了ascill编码,可以看出,它只能被用于英文的文本读取和写入,比如中文汉字的你好或者日文和韩文,就无法使用ascii编码来进行读取和写入了,因为在ascii字符集中,没有表示这些文字的码点,并且ascii也无法表示,因为ascii编码的二进制码点只有1个字节,并且已经全部使用完了.

于是,中国大陆在1981推出GB2312编码以及后续对GB2312进行的拓展GBK编码,港澳台地区于1984年推出Big5.

GB2312编码是对中文汉字的编码,共收入汉字6763个和非汉字图形字符682个。GBK编码,是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从8140至FEFE(剔除xx7F),共23940个码位,共收录了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。GBK编码方案于1995年10月制定, 1995年12月正式发布,中文版的WIN95、WIN98、WINDOWS NT以及WINDOWS 2000、WINDOWS XP、WIN 7等都支持GBK编码方案。

五.出现乱码的原因

介绍了上面关于字符编码的知识点之后,下面就和大家一起看下,为什么会出现乱码. 当我们使用GB2312的字符编码方式编写并保存"你好",然后使用BIG5的字符编码方式打开这个文件就会出现乱码,GB2312和BIG5相同的二进制码点对应的字符是不同的,所以当文本编辑器读取二进制数据的时候,每次读取两个字节后,就会认为这是一个字符(GB2312和BIG5都是每个码点占两个字节),假设你好两个汉字,在GB2312的字符集中的二进制码点是:1010 0110 1111 0000 1011 1011 1011 1011,在big5字符集的二进制码点是1010 0001 0100 0000 1011 1011 1011 1011,所以他们的码点是不同的,但是我们编写的二进制数据在保存那一刻就已经确定了,所以按照其他的不兼容的字符集读取出来就是其他的字符了,也就是出现了乱码.

但是如果我们使用GB2312编写并保存的数据,使用GBK打开,那么是不会出现乱码的,因为GBK是GB2312的拓展,所以是相互兼容的,GB2312中的码点对应的字符在GBK中肯定也是一摸一样的.

六.UTF-8和Unicode

从上面的介绍可以发现,字符集太多了,不同的国家又使用不同的字符集,那么就很容易出现乱码;所以我们急缺一种囊括世界上各类语言的字符的字符集,于是1991年就出现了unicode字符集,unicode字符集几乎涵盖了地球上所有国家的语言的字符,并且还在2010年包含了emoji,随着unicode字符集的不断迭代,截止目前,unicode字符集已经包括了14万个字符.

unicode字符集是兼容ascii字符集的,所以在unicode字符集中,比如字符A的码点仍然是: 十进制65和二进制1000001,这样一个字节是足够的,但是比如中文汉字或者微笑的emoji表情,十进制码点是128512,对应的二进制码点是11111011000000000,所以一个字节肯定是不够的.

另外,我们还不能直接将这个数据跟在上一个字符后面,比如上一个字符是A,下一个字符是微笑表情,那么编辑器读取的时候,会不知道哪个位置才是一个字符结束的位置.所以unicode字符集的编码方式就出现了,也就是UTF-32编码,它是针对unicode字符集进行编码的,utf-32编码方式使用固定的四个字节表示字符码点,也就是说uncode字符集中码点是按照utf-32的编码方式存储在计算机中的,utf-32字符编码,让每个字符都占用4个字节,也就是32位,所以上面说的unicode中的A字符的码点,使用utf-32编码方式存储在计算机中就是:00000000 00000000 00000000 01000001,微笑的emoji表情使用utf-32编码方式存储在计算机中就是:00000000 00000001 11110110 00000000,所以这样就可以知道每次读取四个字节就是一个字符的结束了.

但是utf-32编码存在了一个很明显的问题,因为明明ascii字符只需要一个字节,但是还偏偏前面三个字节都是补0了,所以相当于是原来的四倍空间.并且中文汉字在gbk编码中占用2个字节,但是在utf-32却也是需要四个字节的空间,也就是占用空间是原来的2倍,所以这样十分占用空间.于是就有了拯救世界的utf-8编码方式了,是的,utf-8编码方式也是unicode字符集的一种编码方式,它是像下面这样定义unicode字符集中的码点的:

字符码点完整的二进制数据被分割为不同的部分,然后放在不同的字节中.

也就是说,码点在0-127范围的(也就是原来的ascii字符集数据),我们使用一个字节表示,并且这个字节的最高位是固定为0;以此类推,不同码点范围的字符被定义为不同的占用字节数的大小规则,并且规定了每个字节的开始的固定位的数据,这样就可以方便的在一整个连续的文本数据中,轻松地按照规则识别出一个字符的开始和结束了.

七.常见问题解释

7.1 python3和python2在计算相同的字符串长度时,为什么得到的结果不一致?

首先,python代码文件中的编码方式注释目的是为了告诉 Python 解释器,该文件使用的是哪种编码方式,因为python2使用的是ascii编码方式,所以解释器都是认为一个字节就是一个字符,所以当我们使用len函数计算两个汉字的时候(比如你好),那么得到的结果是长度6,这是因为每个汉字占用3个字节,但是通过ascii编码方式来读取,就会认为总共是六个字节的六个ascii字符,所以输出长度是6; 但是在python3中,因为默认就是utf-8编码,也就是默认是支持unicode解析,所以就可以将你好两个汉字的二进制码点按照utf-8的规则正确解析出来,所以也就是会输出长度2;

7.2 为什么会经常看到乱码����或者汉字"锟斤拷"?

这是因为,比如当我们使用utf-8编码打开原本通过gbk编码写入的文本,那么就可能会出现乱码,出现乱码之后,utf-8编码中有一个特殊的字符,当无法解析或展示一个字符的时候,就使用这个特殊的替换字符来代替:

U+FFFD � REPLACEMENT CHARACTER(替换字符),用于替换一个未知的、不被认可的或无法表示的字符。

有些编辑器刚好就会将一些乱码了无法展示的字符使用� 来替换,所以我们打开后看到的文本很有可能就会有很多个� 字符连在一起的效果,比如:

what is your name? ����� 好吗?

那么,我们打开后,如果又点击了保存,那么就会将上面的带有替换符号�的文本保存下来,这个替换符号在utf-8中的二进制码点是:11101111 10111111 10111101,也就是16进制的EF BF BD,所以多个替换符号连在一起的码点数据就是EF BF BD EF BF BD EF BF BD,而这样的二进制数据如果再被使用GBK编码的文本编辑器打开,或者将这个文件发送给了使用GBK编码的人,那么,打开后就会出现"锟斤拷",这是因为GBK是每个字符占用两个字节来表示,所以这样解析下来刚好可以得到下面的组合:

EF BF BD EF BF BD
|      |     |      |    |     |    ̄          ̄         ̄   锟         斤         拷

7.3 \Uxxxx 的unicode转义字符

我们每次可以在编码的时候看到\u0041或者\U0001F602,这些其实都是unicode的原始码点数据. 所以当我们在python中定义一个字符串,比如下面这样:

path = "C:\Users\test.m"

那么其中的\U就会被认为是unicode码点的转移符号,但是后面又不是一个正确的unicode1码点数据,并且这里其实我们并不是想转义,而只是表示一个文件路径,所以我们可以在python中这样声明这个字符串:

path = r"C:\Users\test.m"

在前面加上r,这样就表示我这个字符串就是要表示原始的字符串含义,不需要转义.

在字符串中,反斜杠(\)通常用于表示转义字符。Unicode 转义序列是一种特殊的转义,用于表示 Unicode 字符。在Python字符串中,Unicode 转义序列以\u或\U开头,后面跟随相应的 Unicode 码点。

\U: 后跟八个十六进制数字,表示一个 32 位的 Unicode 字符。