【转载】计算机程序的思惟逻辑 (6) - 如何从乱码中恢复 (上)?

咱们在处理文件、浏览网页、编写程序时,时不时会碰到乱码的状况。乱码几乎老是使人心烦,让人困惑。但愿经过本节和下节文章,你能够自信从容地面对乱码,恢复乱码。算法

谈乱码,咱们就要谈数据的二进制表示,咱们已经在前两节谈过整数和小数的二进制表示,接下了咱们将讨论字符和文本的二进制表示。浏览器

因为内容比较多,咱们将分两节来介绍。本节主要介绍各类编码,乱码产生的缘由,以及简单乱码的恢复。下节咱们介绍复杂乱码的恢复。编辑器

编码和乱码听起来比较复杂,文章也比较长,但其实并不复杂,请耐心阅读,让咱们逐步来探讨。编码

ASCII代理

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

这个方法是一个标准,称为ASCII编码,全称是American Standard Code for Information Interchange,美国信息互换标准代码。orm

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

咱们先来看数字32到126的含义,以下图所示,除了中文以外,咱们日常用的字符基本都涵盖了,键盘上的字符大部分也都涵盖了。ci

数字32到126表示的这些字符都是可打印字符,0到31和127表示一些不能够打印的字符,这些字符通常用于控制目的,这些字符中大部分都是不经常使用的,下表列出了其中相对经常使用的字符。
it

Ascii 码对美国是够用了,但对别的国家而言倒是不够的,因而,各个国家的各类计算机厂商就发明了各类各类的编码方式以表示本身国家的字符,为了保持与Ascii 码的兼容性,通常都是将最高位设置为1。也就是说,当最高位为0时,表示Ascii码,当为1时就是各个国家本身的字符。

在这些扩展的编码中,在西欧国家中流行的是ISO 8859-1和Windows-1252,在中国是GB2312,GBK,GB18030和Big5,咱们逐个来看下这些编码。

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编码,这个编码与ISO8859-1基本是同样的,区别 只在于数字128到159,Windows-1252使用其中的一些数字表示可打印字符,这些数字表示的含义,以下图所示:

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

HTML5 甚至明确规定,若是文件声明的是ISO 8859-1编码,它应该被看作Windows-1252编码。为何要这样呢?由于大部分人搞不清楚ISO 8859-1和Windows-1252的区别,当他说ISO 8859-1的时候,其实他实际指的是Windows-1252,因此标准干脆就这么强制了。

GB2312

美国和西欧字符用一个字节就够了,但中文显然是不够的。中文第一个标准是GB2312。

GB2312标准主要针对的是简体中文常见字符,包括约7000个汉字,不包括一些罕用词,不包括繁体字。

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

好比,"老马"的GB2312编码是(16进制表示):

C0 CF C2 ED

GBK

GBK创建在GB2312的基础上,向下兼容GB2312,也就是说,GB2312编码的字符和二进制表示,在GBK编码里是彻底同样的。

GBK增长了一万四千多个汉字,共计约21000汉字,其中包括繁体字。

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

须要注意的是,低位字节是从0x40也就是64开始的,也就是说,低位字节最高位可能为0。那怎么知道它是汉字的一部分,仍是一个Ascii字符呢?

其实很简单,由于汉字是用固定两个字节表示的,在解析二进制流的时候,若是第一个字节的最高位为1,那么就将下一个字节读进来一块儿解析为一个汉字,而不用考虑它的最高位,解析完后,跳到第三个字节继续解析。

GB18030

GB18030向下兼容GBK,增长了五万五千多个字符,共七万六千多个字符。包括了不少少数民族字符,以及中日韩统一字符。

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

在两字节编码中,字节表示范围与GBK同样。在四字节编码中,第一个字节的值从0x81到0xFE,第二个字节的值从0x30到0x39,第三个字节的值从0x81到0xFE,第四个字节的值从0x30到0x39。

解析二进制时,如何知道是两个字节仍是四个字节表示一个字符呢?看第二个字节的范围,若是是0x30到0x39就是四个字节表示,由于两个字节编码中第二字节都比这个大。

Big5

Big5是针对繁体中文的,普遍用于台湾香港等地。

Big5包括1万3千多个繁体字,和GB2312相似,一个字符一样固定使用两个字节表示。在这两个字节中,高位字节范围是0x81-0xFE,低位字节范围是0x40-0x7E和0xA1-0xFE。

编码汇总

咱们简单汇总一下上面的内容。

Ascii码是基础,一个字节表示,最高位设为0,其余7位表示128个字符。其余编码都是兼容Ascii的,最高位使用1来进行区分。

西欧主要使用Windows-1252,使用一个字节,增长了额外128个字符。

中文大陆地区的三个主要编码GB2312,GBK,GB18030,有时间前后关系,表示的字符数愈来愈多,且后面的兼容前面的,GB2312和GBK都是用两个字节表示,而GB18030则使用两个或四个字节表示。

香港台湾地区的主要编码是Big5。

若是文本里的字符都是Ascii码字符,那么采用以上所说的任一编码方式都是一同样的。

但若是有高位为1的字符,除了GB2312/GBK/GB18030外,其余编码都是不兼容的,好比,Windows-1252和中文的各类编码是不兼容的,即便Big5和GB18030都能表示繁体字,其表示方式也是不同的,而这就会出现所谓的乱码。

初识乱码

一个法国人,采用Windows-1252编码写了个文件,发送给了一个中国人,中国人使用GB18030来解析这个字符,看到的就是乱码,咱们举个例子:

法 国人发送的是 "Pékin",Windows-1252的二进制是(采用16进制):50 E9 6B 69 6E,第二个字节E9对应é,其余都是Ascii码,中国人收到的也是这个二进制,可是他把它看作成了GB18030编码,GB18030中E9 6B对应的是字符"閗i",因而他看到的就是:"P閗in",这看来就是一个乱码。

反之也是同样的,一个GB18030编码的文件若是被看作Windows-1252也是乱码。

这 种状况下,之因此看起来是乱码,是由于看待或者说解析数据的方式错了。纠正的方式,只要使用正确的编码方式进行解读就能够了。不少文件编辑器,如 EditPlus, NotePad++, UltraEdit都有切换查看编码方式的功能,浏览器也都有切换查看编码方式的功能,如Firefox,在菜单 "查看"->"文字编码"中。

切换查看编码的方式,并无改变数据的二进制自己,而只是改变了解析数据的方式,从而改变了数据看起来的样子。(稍后咱们会提到编码转换,它正好相反)。

不少时候,作这样一个编码查看方式的切换,就能够解决乱码的问题。但有的时候,这样是不够的,咱们稍后提到。

Unicode

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

世界上全部的字符能不能统一编码呢?能够,这就是Unicode。

Unicode 作了一件事,就是给世界上全部字符都分配了一个惟一的数字编号,这个编号范围从0x000000到0x10FFFF,包括110多万。但大部分经常使用字符都 在0x0000到0xFFFF之间,即65536个数字以内。每一个字符都有一个Unicode编号,这个编号通常写成16进制,在前面加U+。大部分中文 的编号范围在U+4E00到U+9FA5,例如,"马"的Unicode是U+9A6C。

Unicode就作了这么 一件事,就是给全部字符分配了惟一数字编号。它并无规定这个编号怎么对应到二进制表示,这是与上面介绍的其余编码不一样的,其余编码都既规定了能表示哪些 字符,又规定了每一个字符对应的二进制是什么,而Unicode自己只规定了每一个字符的数字编号是多少。

那编号怎么对应到二进制表示呢?有多种方案,主要有UTF-32, UTF-16和UTF-8。

UTF-32

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

但有个细节,就是字节的排列顺序,若是第一个字节是整数二进制中的最高位,最后一个字节是整数二进制中的最低位,那这种字节序就叫“大端”(Big Endian, BE),不然,正好相反的状况,就叫“小端”(Little Endian, LE)。对应的编码方式分别是UTF-32BE和UTF-32LE。

能够看出,每一个字符都用四个字节表示,很是浪费空间,实际采用的也比较少。

UTF-16

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

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

区分是两个字节仍是四个字节表示一个符号就看前两个字节的编号范围,若是是U+D800到U+DBFF,就是四个字节,不然就是两个字节。

UTF-16也有和UTF-32同样的字节序问题,若是高位存放在前面就叫大端(BE),编码就叫UTF-16BE,不然就叫小端,编码就叫UTF-16LE。

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

UTF-8

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

具体来讲,各个Unicode编号范围对应的二进制格式以下图所示:

图中的x表示能够用的二进制位,而每一个字节开头的1或0是固定的。

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

对于一个Unicode编号,具体怎么编码呢?首先将其看作整数,转化为二进制形式(去掉高位的0),而后将二进制位从右向左依次填入到对应的二进制格式x中,填完后,若是对应的二进制格式还有没填的x,则设为0。

咱们来看个例子,'马'的Unicode编号是:0x9A6C,整数编号是39532,其对应的UTF-8二进制格式是:

1110xxxx 10xxxxxx 10xxxxxx

整数编号39532的二进制格式是 1001 101001 101100

将这个二进制位从右到左依次填入二进制格式中,结果就是其UTF-8编码:

11101001 10101001 10101100

16进制表示为:0xE9A9AC

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

Uncode编码小结

Unicode给世界上全部字符都规定了一个统一的编号,编号范围达到110多万,但大部分字符都在65536之内。Unicode自己没有规定怎么把这个编号对应到二进制形式。

UTF- 32/UTF-16/UTF-8都在作一件事,就是把Unicode编号对应到二进制形式,其对应方法不一样而已。UTF-32使用4个字节,UTF-16 大部分是两个字节,少部分是四个字节,它们都不兼容Ascii编码,都有字节顺序的问题。UTF-8使用1到4个字节表示,兼容Ascii编码,英文字符 使用1个字节,中文字符大多用3个字节。

编码转换

有了Unicode以后,每个字符就有了多种不兼容的编码方式,好比说"马"这个字符,它的各类编码方式对应的16进制是:

GB18030 C2 ED
Unicode编号 9A 6C
UTF-8 E9 A9 AC
UTF-16LE 6C 9A

这几种格式之间能够借助Unicode编号进行编码转换。能够简化认为,每种编码都有一个映射表,存储其特有的字符编码和Unicode编号之间的对应关系,这个映射表是一个简化的说法,实际上多是一个映射或转换方法。

编码转换的具体过程能够是,好比说,一个字符从A编码转到B编码,先找到字符的A编码格式,经过A的映射表找到其Unicode编号,而后经过Unicode编号再查B的映射表,找到字符的B编码格式。

举例来讲,"马"从GB18030转到UTF-8,先查GB18030->Unicode编号表,获得其编号是9A 6C,而后查Uncode编号->UTF-8表,获得其UTF-8编码:E9 A9 AC。

与前文提到的切换查看编码方式正好相反,编码转换改变了数据的二进制格式,但并无改变字符看上去的样子。

再看乱码

在前文中,咱们提到乱码出现的一个重要缘由是解析二进制的方式不对,经过切换查看编码的方式就能够解决乱码。

但若是怎么改变查看方式都不对的话,那颇有可能就不只仅是解析二进制的方式不对,而是文本在错误解析的基础上还进行了编码转换。

咱们举个例子来讲明:

  1. 两个字 "老马",原本的编码格式是GB18030,编码是(16进制): C0 CF C2 ED。
  2. 这个二进制形式被错误当成了Windows-1252编码, 解读成了字符 "ÀÏÂí"
  3. 随后这个字符进行了编码转换,转换成了UTF-8编码,形式仍是"ÀÏÂí",但二进制变成了:C3 80 C3 8F C3 82 C3 AD,每一个字符两个字节。
  4. 这个时候,再按照GB18030解析,字符就变成了乱码形式"脌脧脗铆", 并且这时不管怎么切换查看编码的方式,这个二进制看起来都是乱码。

这种状况是乱码产生的主要缘由。

这种状况其实很常见,计算机程序为了便于统一处理,常常会将全部编码转换为一种方式,好比UTF-8, 在转换的时候,须要知道原来的编码是什么,但可能会搞错,而一旦搞错,并进行了转换,就会出现这种乱码。

这种状况下,不管怎么切换查看编码方式,都是不行的。

那有没有办法恢复呢?若是有,怎么恢复呢?

相关文章
相关标签/搜索