精通字符编码

编码有两大类:一类是非Unicode编码;另一类是Unicode编码。我们先介绍非Unicode编码。

非Unicode编码

类型

介绍

规则

ASCII

历史

世界上虽然有各种各样的字符,但计算机发明之初没有考虑那么多,基本上只考虑了美国的需求。美国大概只需要128个字符,所以就规定了128个字符的二进制表示方法。

ASCII编码:全称是American Standard Code for InformationInterchange,即美国信息互换标准代码。

128个字符用7位刚好可以表示,计算机存储的最小单位是byte,即8位,ASCII码中最高位设置为0,用剩下的7位表示字符。这7位可以看作数字0~127, ASCII码规定了从0~127的每个数字代表什么含义。

32~126:可打印字符。

0~31和127:一些不可以打印的字符,这些字符一般用于控制目的。

ASCII码对美国是够用了,但对其他国家而言却是不够的,于是,各个国家的各种计算机厂商就发明了各种各种的编码方式以表示自己国家的字符,为了保持与ASCII码的兼容性,一般都是将最高位设置为1。也就是说,当最高位为0时,表示ASCII码,当为1时就是各个国家自己的字符

ISO 8859-1

ISO 8859-1又称Latin-1。西欧国家使用。

使用一个字节表示一个字符。

其中0~127与ASCII一样,128~255规定了不同的含义。在128~255中,128~159表示一些控制字符。

160~255表示一些西欧字符。

Windows-1252

ISO 8859-1虽然号称是标准,用于西欧国家,但它连欧元(€)这个符号都没有,因为欧元比较晚,而标准比较早。实际中使用更为广泛的是Windows-1252编码。

这个编码中加入了欧元符号以及一些其他常用的字符。基本上可以认为,ISO 8859-1已被Windows-1252取代,在很多应用程序中,即使文件声明它采用的是ISO 8859-1编码,解析的时候依然被当作Windows-1252编码。

这个编码与ISO 8859-1基本是一样的,区别只在于数字128~159,Windows-1252使用其中的一些数字表示可打印字符。

GB2312

美国和西欧字符用一个字节就够了,但中文显然是不够的。中文第一个标准是GB2312。GB2312标准主要针对的是简体中文常见字符,包括约7000个汉字和一些罕用词和繁体字。

GB2312固定使用两个字节表示汉字,在这两个字节中,最高位都是1,如果是0,就认为是ASCII字符。在这两个字节中,其中高位字节范围是0xA1~0xF7,低位字节范围是0xA1~0xFE。

GBK

GBK建立在GB2312的基础上,向下兼容GB2312,也就是说,GB2312编码的字符和二进制表示,在GBK编码里是完全一样的。GBK增加了14 000多个汉字,共计约21 000个汉字,其中包括繁体字。

GBK同样使用固定的两个字节表示,其中高位字节范围是0x81~0xFE,低位字节范围是0x40~0x7E和0x80~0xFE。

需要注意的是,低位字节是从0x40(也就是64)开始的,也就是说,低位字节的最高位可能为0。那怎么知道它是汉字的一部分,还是一个ASCII字符呢?其实很简单,因为汉字是用固定两个字节表示的,在解析二进制流的时候,如果第一个字节的最高位为1,那么就将下一个字节读进来一起解析为一个汉字,而不用考虑它的最高位,解析完后,跳到第三个字节继续解析。

GB18030

GB18030向下兼容GBK,增加了55 000多个字符,共76 000多个字符,包括了很多少数民族字符,以及中日韩统一字符。

用两个字节已经表示不了GB18030中的所有字符,GB18030使用变长编码,有的字符是两个字节,有的是四个字节。

在两字节编码中,字节表示范围与GBK一样。

在四字节编码中

  • 第一个字节的值为0x81~0xFE
  • 第二个字节的值为0x30~0x39
  • 第三个字节的值为0x81~0xFE
  • 第四个字节的值为0x30~0x39

解析二进制时,如何知道是2个字节还是4个字节表示一个字符呢?看第二个字节的范围,如果是0x30~0x39就是4个字节表示,因为两个字节编码中第二个字节都比这个大。

Big5

Big5是针对繁体中文的,广泛用于我国台湾地区和我国香港特别行政区等地。Big5包括13 000多个繁体字。

和GB2312类似,一个字符同样固定使用两个字节表示。在这两个字节中,高位字节范围是0x81~0xFE,低位字节范围是0x40~0x7E和0xA1~0xFE。

以上我们介绍了中文和西欧的字符与编码,但世界上还有很多其他国家的字符,每个国家的各种计算机厂商都对自己常用的字符进行编码,在编码的时候基本忽略了其他国家的字符和编码,甚至忽略了同一国家的其他计算机厂商,这样造成的结果就是,出现了太多的编码,且互相不兼容。

Unicode编码

Unicode:世界上所有的字符统一编码,给世界上所有字符都分配了一个唯一的数字编号,这个编号范围从0x000000~0x10FFFF,包括110多万。

大部分常用字符都在0x0000~0xFFFF之间,即65 536个数字之内。

每个字符都有一个Unicode编号,这个编号一般写成十六进制,在前面加U+。

大部分中文的编号范围为U+4E00~U+9FFF。

Unicode本身只规定了每个字符的数字编号是多少,没有规定字符编号怎么对应到二进制表示。

编码的二进制表示主要有UTF-32、UTF-16和UTF-8。

类型

介绍

规则

UTF-32

这个最简单,就是字符编号的整数二进制形式,固定4个字节。

每个字符都用4个字节表示,非常浪费空间,实际采用的也比较少。

UTF-16

UTF-16使用变长字节表示。

UTF-16常用于系统内部编码,UTF-16比UTF-32节省了很多空间,但是任何一个字符都至少需要两个字节表示,对于美国和西欧国家而言,还是很浪费的。

  • 对于编号在U+0000~U+FFFF的字符(常用字符集)​,直接用2个字节表示。需要说明的是,U+D800~U+DBFF的编号其实是没有定义的。
  • 字符值在U+10000~U+10FFFF的字符(也叫做增补字符集)​,需要用4个字节表示。前两个字节叫高代理项,范围是U+D800~U+DBFF;后两个字节叫低代理项,范围是U+DC00~U+DFFF。数字编号和这个二进制表示之间有一个转换算法,本书就不介绍了。

区分是两个字节还是4个字节表示一个字符就看前两个字节的编号范围,如果是U+D800~U+DBFF,就是4个字节,否则就是两个字节。

UTF-8

UTF-8使用变长字节表示,每个字符使用的字节个数与其Unicode编号的大小有关,编号小的使用的字节就少,编号大的使用的字节就多,使用的字节个数为1~4不等。

和UTF-32/UTF-16不同,UTF-8是兼容ASCII的,对大部分中文而言,一个中文字符需要用三个字节表示。

  • 小于128的,编码与ASCII码一样,最高位为0。其他编号的第一个字节有特殊含义,最高位有几个连续的1就表示用几个字节表示,而其他字节都以10开头。

UTF-8编码的编号范围与对应的二进制格式(图中的x表示可以用的二进制位,而每个字节开头的1或0是固定的):

一个Unicode编号编码的过程:

  1. 首先将其看作整数,转化为二进制形式(去掉高位的0)
  2. 然后将二进制位从右向左依次填入对应的二进制格式x中
  3. 填完后,如果对应的二进制格式还有没填的x,则设为0。

举个例子:“马”的Unicode编号是0x9A6C

  1. 整数编号是39 532,二进制为1001 101001 101100
  2. 根据编号范围选择上图中的第三档,二进制形式为 1110xxxx 10xxxxxx 10xxxxxx
  3. 把二进制依次填入上述形式为 11101001 10101001 10101100,十六进制表示为0xE9A9AC

注意字节的排列顺序,如果第一个字节是整数二进制中的最高位,最后一个字节是整数二进制中的最低位,那这种字节序就叫“大端”(Big Endian, BE),否则,就叫“小端”(Little Endian, LE)。

编码转换及乱码

编码转换的具体过程可以是:一个字符从A编码转到B编码,先找到字符的A编码格式,通过A的映射表找到其Unicode编号,然后通过Unicode编号再查B的映射表,找到字符的B编码格式。

乱码有两种常见原因:

乱码原因

说明

恢复

简单的解析错误

之所以看起来是乱码,是因为看待或者说解析数据的方式错了。

例子

一个法国人采用Windows-1252编码写了个文件,发送给了一个中国人,中国人使用GB18030来解析这个字符,看到的可能就是乱码。比如,法国人发送的是Pékin, Windows-1252的二进制(采用十六进制)是50 E9 6B 69 6E,第二个字节E9对应é,其他都是ASCII码,中国人收到的也是这个二进制,但是他把它看成了GB18030编码,GB18030中E9 6B对应的是字符“閗”​,于是他看到的就是“P閗in”​,这看来就是一个乱码。

切换查看编码的方式并没有改变数据的二进制本身,而只是改变了解析数据的方式,从而改变了数据看起来的样子

在错误解析的基础上进行了编码转换

文本在错误解析的基础上还进行了编码转换。

例子

  1. 两个字“老马”​,本来的编码格式是GB18030,编码(十六进制)是C0 CF C2ED。
  2. 这个二进制形式被错误当成了Windows-1252编码,解读成了字符“ÀÏÂí”​。
  3. 随后这个字符进行了编码转换,转换成了UTF-8编码,形式还是“ÀÏÂí”​,但二进制变成了C3 80 C3 8F C3 82 C3 AD,每个字符两个字节。
  4. 这个时候再按照GB18030解析,字符就变成了乱码形式“脌脧脗铆”​,而且这时无论怎么切换查看编码的方式,这个二进制看起来都是乱码。

这种情况是乱码产生的主要原因。

“乱”主要是因为发生了一次错误的编码转换,所谓恢复,是指要恢复两个关键信息:

  • 一个是原来的二进制编码方式A;
  • 另一个是错误解读的编码方式B(进行了转换)。

恢复的基本思路是尝试进行逆向操作,假定按一种编码转换方式B获取乱码的二进制格式,然后再假定一种编码解读方式A解读这个二进制,查看其看上去的形式,这要尝试多种编码,如果能找到看着正常的字符形式,应该就可以恢复。

如何判断恢复了?如果能找到看上去对的形式,就恢复了。

通过代码循环上述方法尝试多种编码方式,如果输出有正确的,那么就可以恢复。

有些情况很难恢复:

  • 如果形式中有很多不能识别的字符(如?​)
  • 如果乱码是由于进行了多次解析和转换错误造成的

#面试___岗的必刷题单##AI求职记录#
全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务