原文地址:http://blog.sina.com.cn/s/blog_6364576a0100gs7q.html
概念上的澄清:如果有人问你知道不知道unicode呢?你可能会说,我知道,不就是“统一字符编码标准”嘛。对的,你回答的没错,但别人又问你,utf-8是什么呢?你可能会说,utf-8就是unicode。这个回答就不够准确了。Unicode和ASCII、GB2312一样,是一种编码标准。平常更随意的说话是简称为“编码”。这里“编码”的概念,实际上包括了两方面的含义,其一是指该编码所对应的字符集;另一方面是指具体的编码方案,也就是我们通常所说的内码格式。所谓字符集就是指该编码方案包括哪些字符,比如ASCII编码一共包括128个字符,其中有英文大小写字母、数字、标点符号以及一些控制字符。而编码方案是指如何把这128个字符用字节码表示出来,比如说,是把英文字母“A”对应为第1号字符,还是对应为第N号字符呢。在ASCII编码中把字母“A”对应为第65号字符,十六进制表示就是 0x41。
首先来说说最通用的ASCII编码(美国资讯交换标准码)。ASCII编码的128个字符正好可以被一个字节的前七个bit位来表示,而最高的第八个bit位置为0就可以了。几乎现在的所有的编码标准都是和ASCII编码相兼容的。所谓兼容,就是指,新的编码只是扩充ASCII编码,但一个字节的前128个字符都是和ASCII编码完全一致的。我们可以看出,ASCII编码针对英语来讲是足够了的,但它无法表示像中文这样的庞大的字符系统,所以各个国家都针对自己民族语言的特点来扩充ASCII编码。像德语这样的语言,字符元素的数量和英语差不多,所以完全可利用一个字节的剩下的128个字符位置来给自己的语言编码。但对东方语言,事情就没那么简单了,最为庞大的就是汉语了,光简体字形就六、七千个,再加上繁体字、生辟字、古汉字、少数民族的字形以及一些常用的符号、标志啦,总数是在万到十万的量级上。而单字节的最大容量只有256,显然是不能满足汉语的要求的。
双字节的容量有多大呢?256乘以256等于65536,双字节有六万多的空间,这足以满足我们日常的需求了。我们最常用的GB2312、GBK编码标准就都是支持双字节的编码标准,当然,它们也是和ASCII码兼容的。对于每一种编码标准我们都要从字符集和编码方案两个方面去了解。GB2312的字符集,其中除了与ASCII码兼容的那些字符外,再就是包括I级汉字3755字、II级汉字3008字,共计6,763个汉字,另外还有一些符号和标志字符。GB2312的编码方案有两种,一种是用得最为广泛的,就是EUC变字节编码方式。可以用单字节也可以用双字节,为了能区分单字节字符和双字节字符的界限,要求双字节的第一个字节的最高位必须置为1,以就是说,当软件读到一个字节,发现它的值大于128了,就认为它不是一个与ASCII码兼容的字符了,而是一个双字节字符的第一个字节,要连同下一个字节,一起被解读为一个字符。变字节方式的编码方案做到了对单字节编码的扩展,并且可与单字节编码相兼容,但有一个缺点就是在字数统计上变得困难了。另一种编码方案是HZ编码方式,由于现在用的非常少,所以就不介绍了。
GB2312中的汉字全是简体汉字,为了能应用繁体汉字,就有了GB12345标准。GB12345标准的字符集是与GB2312字符集相对应的汉字的繁体字型,当然还有一些增加的字符。它的编码方案与GB2312完全相同,也就是说,同样的一个双字节字符,比如0xB9FA,在GB2312中表示的是“国”字,而在GB12345中表示的就是“國”字。这样表示繁体字有一个弱点,就是无法同时显示简体和繁体字,因为你不能让软件对这个字符用这个编码标准,而对另一个字符用另一编码标准,即使软件支持这样的功能,这一切也太过复杂了。而且随着信息的国际化,越来越多的要求把多种语言集成在一起,比如说一份文档中,不仅含有英文、简体中文和繁体中文,还包括日语和韩语文本。基于这些的考虑,就有了GBK编码标准,GBK标准的字符集共包含20902个汉字、日语中的平假、片假名、日语中的汉字、韩语中的字型以及一些数学符号、标志符号等等。两万多的汉字,基本上覆盖了所有的简体和繁体汉字,以及大部分的生辟字等。GBK的编码方案也是变字节的,并且和GB2312完全兼容,只是在GB2312双字节区域中扩展了编码范围,比如说,GB2312中双字节的第一个字节只能在0xA1~0xFE范围内,而GBK则扩展为在0x81~0xFE范围内。为了与GB2312兼容,在排定了GB2312字符集的位置后,再在扩展的位置上指定剩下的字符。所以原有的GB2312的文档在GBK中都是能正确显示的。
故事到这里应该是已经很完美了,但是还差一些,让我们继续我们的编码探秘之旅。随着计算机在各个领域中的使用,大量的特殊字符涌现出来了,比如说偏微分符号、商标注册符号、五线谱的高音谱号。另一方面就是在学术中要求对古语言字型和各少数民族字型的支持。出于以上这些考虑,就要求有更庞大的编码标准,于是乎双字节的65536个空间也显得不够用了。只能要求更多的字节来容纳这些新的字符。中国恰好是一个历史悠久又多民族的国家,为了能把古汉语的字型以及蒙语、藏语等多民族的语言容纳进来,于是出台了GB18030标准,该字符集光汉字就包括了27533个,还有其它的字符等等。GB18030的编码方案也是变字节的,它支持四个字节的字符。GB18030与GBK是兼容的,GB18030的双字节汉字对应的就是GBK字符集中的所有汉字,其余的6631个汉字是用四字节编码的。
上面的所有只是中国一脉的编码发展,而别的国家的编码方案也是纷繁的,这样的话,在软件的国际化上将变得非常困难。国际标准组织为各国的各种编码标准定义了一个编号,叫做编码页CP(Code Page),比如说,GBK是CP936、GB2312是CP20936、GB18030是CP54936、BIG5是CP950,我们平常用的英文环境是ANSI-Latin I,其编码页是CP1252。不同的编码标准一般情况是不互相兼容的,比如GBK和BIG5。而不同的编码标准的字符集也是不一样的。这在文档的共享和软件的设计上都会产生困难,所以大家需要一种公共的编码方案来整合所有的编码标准,这就是——Unicode。
同样,Unicode规范也分别定义了它的字符集和编码方案。Unicode的字符集很庞大,基本上包括了世界各地的语言和各个领域中的符号,而且随着Unicode标准的更新,更多的字符将被包括进来。单从汉语上来讲,在Unicode2.0时,它支持的中文与GBK中的完全一样,共计20902个汉字;而Unicode3.0,在扩展A区中,又添加了6582个汉字,共计27496个汉字,这与GB18030的容量基本上差不多了。而在Unicode3.1中,还会在扩展B区中添加更多的字符支持。Unicode字符集的定义是抽象的,它只是指定了一个序列,比如说:
U+0020 : SPACE
U+0041 : LATIN CAPITAL LETTER A
U+0042 : LATIN CAPITAL LETTER B
其中的U+0020表示十六进制的第20位的字符是一个叫SPACE的字符,也就是我们通常说的空格符。U+表示这是一个Unicode字符集序列号。这与该字符的实际编码方案是没关系的,完全可以把这个空格符编成任何一个字节形式,而不一定要与0x20相对应。这正是编码方案所要定义的。
如果我们的文档保存的大部分信息是英文信息,其中只有很少的中文,那么采用utf-16编码方案就很不经济了,因为每个英文字母都是用两个字节保存的,这样会保存了大量冗余字节0x00。另外一个坏处就是,如果在网络上传输这样的文件,如果由于网络的原因,造成传输中有缺损字节,那么整个utf-16的文档将变成乱码了,因为,你无法知道该文件中的一个字节是Unicode字符的第一个字节还是第二个字节。还有就是utf-16编码方案是与ASCII编码不兼容的。等等这些,都要求我们要有一种更好的编码方案,那就是utf-8。作为Unicode的一种编码方案,它支持的字符集当然是和utf-16是一样的,都是Unicode字符集。utf-8是变字节的编码方案,随着序号的增加,它会从单字节、双字节一直到三字节来表示一个字符,在字符集扩展后,还会出现四字节、五字节的字符。从Unicode字符集序号到utf-8的编码规则如下表显示:
U-00000000 - U-0000007F: | 0xxxxxxx |
U-00000080 - U-000007FF: | 110xxxxx 10xxxxxx |
U-00000800 - U-0000FFFF: | 1110xxxx 10xxxxxx 10xxxxxx |
U-00010000 - U-001FFFFF: | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
U-00200000 - U-03FFFFFF: | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
U-04000000 - U-7FFFFFFF: | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
例如:
Unicode字符U+00A9 = 1010 1001(版权符号)在utf-8 里的编码为:
11000010 10101001 = 0xC2 0xA9
而字符 U+2260 = 0010 0010 0110 0000 (不等于符号) 编码为:
11100010 10001001 10100000 = 0xE2 0x89 0xA0
可以看出utf-8是与ASCII码兼容的,所以它会单字节存储ASCII码字符,而汉字将会用三个字节来存储,这样用utf-8来保存全汉字的文件是效率不高的。另外,可看出按照这种编码,第一个字节前面二进制1的个数就是该字符所用的字节数。当丢失了一段字节时,可以很容易判定下一个字符的起使位置。utf-8的编码标志字节是0xEF BB BF。