ASCII

计算机中任何信息都是二进制存储的,存储的最小单位是位,8位为一个字节。一位有0和1两种状态表示,因此有2的8次方(256)种组合方式,每种代表一个字符。ASCII码对英语字符与二进制位之间的关系,做了统一规定。它规定有128个字符,占0-127位,包括最基本的英文字母、标点符号。ASCII又被称为7位码,因为它实际运用到的只有7位来编码,而第一位总是为0。例如,空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。

ANSI

因为ASCII码最多只能表示256种字符,很多国家就开发了自己文字编码。简体中文GB2312,繁体中文的BIG5,日文的JIS,这些编码统称为ANSI。拿GB2312来说,它用一个字节来表示英文的字母和标点,编码规则和ASCII一样,做到了向下兼容ASCII,而汉字字符就用两个字节。但是GB2312编码的汉字只有几千个,不包含一些生僻的字,导致有一些人名无法显示。后来升级到GBK,GBK包括了GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。现在最新的是GB18030,加入了一些国内少数民族的文字,一些生僻字被编到4个字节,每扩展一次都完全保留之前版本的编码,所以每个新版本都向下兼容。

Unicode

虽然ANSI解决了自己的编码问题,但是不同国家之间谁也不懂谁的编码,Unicode就是为了解决这种乱码问题。Unicode由ISO国际组织设计,可以容纳全世界所有语言文字的编码方案。其中每个字符都有唯一与之对应的编码,比如说简体汉字丑的Unicode是4E01(二个字节),繁体汉字的醜的Unicode是919C(二个字节)。而对于一些比较靠后的字符,其编码可达到四个字节。Unicode解决了传统的字符编码方案的局限,但是它本身也存在一些问题:

  • 在ASCII中,英文字母只用一个字节表示就够了。如果Unicode统一规定,每个字符用二个字节表示,这对于存储空间来说是极大的浪费。

  • 如何才能区别Unicode和ASCII?计算机怎么知道二个字节表示一个字符,而不是分别表示两个字符。

Unicode在很长一段时间内无法推广,直到互联网的出现。为解决Unicode如何在网络上传输的问题,面向传输的众多 UTF(UCS Transfer Format)标准出现了。其主要的有UTF-8与UTF-16,此外还有比较少见的UTF-32与UTF-7。这里的概念要搞清,Uniocde只是一套字符集,收录字符与编码的对应规则,但是它没有规定这些编码如何传输,存储。而UTF是Uniocde的具体实现方式

UTF-16

UTF-16以每2个字节为一个单元,每个字符由1-2个单元组成,所以每个字符可能是2个字节或者4个字节,包括最常见的英文字母都会编成两个字节。大部分汉字也是2个字节,少部分生僻字为4个字节。在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?

Unicode规范中推荐的标记字节顺序的方法是”Byte Order Mark”,简称BOM。在Unicode编码中有一个叫做”ZERO WIDTH NO-BREAK SPACE”的字符,它的编码是FEFF。而FFFE在Unicode中是不存在的字符,所以不应该出现在实际传输中。Unicode规范建议我们在传输字节流前,先传输字符”ZERO WIDTH NO-BREAK SPACE”。这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的,即为UTF-16BE;如果收到FFFE,就表明这个字节流是Little-Endian的,即即为UTF-16LE。

有时,我们会见到USC-2与USC-4,这两货又是什么呢?早期Unicode收编的字还不多时,两个字节足够表示所有字符,所以有一种固定为两个字节的UTF,叫UCS-2。UTF-16的两个字节部分和UCS-2完全一样,所以UTF-16向下兼容UCS-2。UCS-2同样分LE和BE。同样的少见的UFT-32,其实也就是USC-4。

UTF-16也有一个很大的问题,由于UTF-16只能由2个字节或者4个字节组成,它不兼容ASCII码。而UTF-8的出现,就能很好的解决这两个问题

UTF-8

UTF-8完全兼容ASCII码,它可以使用1~4个字节表示一个字符,根据不同的字符而变化字节长度。 UTF-16的编码与Unicode一样,但是从Unicode到UTF-8却不是直接的对应,而是要通过一些算法和规则来转换。UTF-8的编码规则很简单,只有二条:

  • 对于单字节的字符,字节的第一位为0,后面7位为这个符号的Unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。

  • 对于n字节的字符(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的Unicode码。

  • UTF8

例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001,用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。相应的,UTF-8也有BOM,”ZERO WIDTH NO-BREAK SPACE”字符在UTF-8中的编码为EF BB BF 。所以一般检测到BOM为EF BB BF,就知道此文件编码格式是UTF-8了。

笔记本记录联通再次打开错码

在记事本保存操作中,windows默认保存的编码是ANSI(在中国是GBK)。

“联”ANSI编码是 0xC1AA , 二进制排列是 11000001 10101010
“通”ANSI编码是 0xCDA8 , 二进制排列是 11001101 10101000

这两个字的二进制是不是看起来好眼熟,没错就是和上面的UTF-8编码规则相似。

UTF8UTF8

“联通”这两个字的ANSI编码符合UTF8编码的第二个模板。“联”的两个字节、“通”的两个个字节的起始部分的都是”110”和”10”,正好与UTF8规则里的两字节模板是一致的,于是再次打开记事本时,记事本就误认为这是一个UTF8编码的文件,猜错了导致乱码。当然不止联通,所有的编码在【 11000000<=第一个字节<=11011111】 【 10000000 <=第二个字节<= 10111111】 的字符都出现乱码,例如“力挺联通”也会出现乱码。

扯了这么多,那么这个问题怎么解决呢。其实很简单,记事本保存时,有一个另存为的菜单,选择编码为UTF-8就可以了。

save