[转]Unicode演义

来源:互联网 发布:b2b2c源码 出售 编辑:程序博客网 时间:2024/05/01 00:20

转自 乌贼的一言堂:http://woolzey.bokee.com/6749370.html

关键词: UTF-8 GB18030

前Unicode时代

在Unicode出来,我们最熟悉的编码是ASCII。ASCII严格的说是一个7位的英语编码标准,定义了33个控制字符和95个可显示字符。第8位留作奇偶校验,但是通常都置为0。

ASCII只适用于英语,缺少其他语言的支持。IBM最早使用“代码页”的方法映射不同的语言。一个代码页其实是就是某种特定映射方式的称呼,通常情况下,前 128个字符的映射和ASCII大致相同。当然代码页的用途并不只是为了处理不同语言。

最常见的代码页应该是cp437了,它把ASCII码中的控制字符映射成笑脸之类的符号,并且扩充了128个其他符号,其中包括很多制表符。80年代的计算机在文本模式下大多采用这种代码页来显示。

对不同语言,使用不同的代码页就可以了,但是如果想同时处理不同代码页上的语言,就没有简单的办法了,因为同样一个8位数据在不同的代码页上可能被映射成为不同的文字。所以早期的计算机处理多语言很困难,国际交流比较困难,这个在网络时代是一个重大缺陷。这就是Unicode(统一编码、通用编码)出现的原因。

在Unicode时代,代码页被微软发扬光大,现在的代码页基本上可以看作只是单纯的编码方式的叫法,比如UTF-8在Windows的代码页是65001。

Unicode和ISO 10646

在80年代中,ISO组织开始编排一个统一的字符集,并期望这个字符集标准可以解决计算机处理多语言的问题。这就是后来的ISO 10646。

最早的ISO 10646使用31比特进行编码,因此理论上可用的编码空间可以容纳2的 31次方个字符。不过ISO 10646禁止编码中出现0x00到0x1f,以及0x80到0x9f这些字节,因此实际的容量大约6亿多。

在80年代储存容量都是以K计算的,如果采用ISO 10646就意味着需要使用4个字节编码一个字符,非常浪费空间,特别对原来只需要一个字节就好了的西方语言是无法接受的。因此绝大多数软件公司极力抵制ISO 10646,一些大公司联合起来制定了一个新的统一字符集,这就是Unicode。

Unicode使用16比特编码字符,同时也不需要避免出现特殊的控制字符,因此理论上最多可以容纳65536个字符。这在当时看是足够了,同时也能避免大量浪费储存空间。在Unicode中,每个字符被赋予一个序号,一般常用"U+"跟序号的十六进制来表示一个Unicode字符(比如汉字“人”就是U+4eba)。这样一方面可以比较精确的说明字符,另一方面也方便了没有安装相应字库的读者。

因为得到业界的支持,Unicode在早期的国际化软件中大量采用。而ISO 10646的处境就比较凄凉了。ISO组织大概也意识到了这个问题,因此开始向Unicode靠拢。后续版本重新编排了字符集,和Unicode保持一致,并且随着Unicode的更新而更新自己的字符集,也放弃了不使用控制字符编码的原则。Unicode事实上也意识到16比特可能不能满足未来的应用,因此从版本3.1开始扩充到U+10ffff,也就是20比特多一点,不到21比特。为了保持一致,ISO也承诺在自己的标准中不使用超过U+10ffff的编码空间。这样一来,两个标准就几乎趋同了。现在, Unicode和ISO 10646几乎完全相同,唯一的区别是Unicode标准还定义了一些辅助算法,所以大家一般也把这两者当成一回事,并不做特别的区别。由于 Unicode的名字比较好听,所以现在一般也用Unicode来指代统一了的字符集,并不一定特指Unicode这个标准。

Unicode中最早的U+0000到U+ffff称为“基本多文种平面”(BMP),从U+10000到 U+1ffff称为第一辅助平面,从U+20000到U+2ffff称为第二辅助平面,以此类推一直到第16辅助平面。一般的原则是目前还有使用的字符放在基本多文种平面中,历史上存在过但是现在已经不使用了的文字放在辅助平面中。现在第3到第13 辅助平面尚未使用。总共17个平面的可用编码容量是一百一十万还多一点,这也是常说的Unicode可以编码一百万字符的来由。

传统的ASCII码在Unicode中仍然排在U+0000到U+007f之间,常用汉字在Unicode 中编排在U+4E00到U+9FBF。这一段实际上也叫“中日韩统一表意符号”,也包括了日文和韩文。BMP中还有其他段是一些不常用的汉字和中文使用的符号,比如 U+3400到U+4DBF的“CJK统一表意符号扩展A”。更加少用的字被编排在第二辅助平面,所以这个平面也称为“表意文字补充平面”。第三辅助平面虽然现在尚未使用,但是相信也会用来存放中日韩表意文字。

1993年Unicode 1.1版本中有20902个统一汉字(包括日韩特有的);2000年出版的Unicode 3.0中增加了6582个统一汉字,总数达到了27496;2001年3.1版中由于增加了辅助平面,因此汉字数增加到了70207;2003年Unicode 4.1增添了22个香港常用字和GB18030中的汉字。预计将来统一汉字总数会增加到10万。

Unicode的编码

UCS-2和UCS-4

字符集统一之后,如何在计算机中表示其中的字符还是一个问题。最直接的方法就是直接使用字符在字符集中的序号,比如U+XXYY在计算机中就用两个字节XX YY来表示。这就是UCS-2。ISO 10646早期的31位编码和现在Unicode辅助平面上的字符需要4个字节来表示,因此叫做UCS-4。

UCS来自于ISO 10646。最早的ISO 10646不使用特殊字节,因此UCS-2和UCS-4在使用上没有问题。但是随着标准的演变,现在的Unicode序号并没有特意避开特殊字节,因此文本的字节流中会出现控制字符字节,对大量以字节为单位处理文字的程序来说会造成严重问题。比如C语言以0x00来表示字符串的结束,如果字符串中出现汉字“一”(U+4e00),那么程序会错误的认为字符串到此结束;又比如汉字“上”是U+4e0a,其中又出现了换行符0x0a。所以不论UCS-2还是UCS-4,通常只作为程序的内码使用,而不常作为交换编码,或者说外部储存编码。

UCS-4也叫UTF-32,在Unicode标准中有定义。UCS-2因为无法表示辅助平面上的字符,所以现在已经过时,不推荐使用。

UTF-16

UTF-16可以说是UCS-2的补丁升级版。由于早期Unicode只有16位,因此软件厂商大多使用UCS-2作为软件的内部编码。在Unicode扩充到辅助平面后,如果要升级软件,大的改动当然不可以,因此就在UCS-2的基础上做了一些小的修改来支持辅助平面。这就是UTF-16。

UTF-16中BMP上的字符,完全和UCS-2相同,使用一个16位来表示;对辅助平面上的字符,使用两个16位来表示。如果用二进制表示就是:

0 0000 xxxx xxxx xxxx xxxx -> xxxxxxxx xxxxxxxx
y yyyy xxxx xxxx xxxx xxxx -> 110110zz zzxxxxxx, 110111xx xxxxxxxx

其中zzzz=yyyyy-1。因为zzzz最多到0x0f,所以yyyyy最大是0x10。这也是为什么 Unicode编码到U+10ffff这么奇怪的位置,而不是选择正好20位或者21位。

因为U+D800(11011000 00000000)到U+DFFF(11011111 11111111)被用于扩展 UTF-16,所以这个区间Unicode不用来编排字符。

UTF-16在商业软件中使用相当广泛。比如Windows,比如java,比如.Net。在自由软件中,KDE和Python也使用UTF-16作为内部编码。UTF-16不兼容ASCII码,并且和UCS-2一样,会出现特殊字节,因此大多也只用于内码,而不是外部交换编码。

UTF-8

为了避开特殊字节, Rob Pike和Ken Thompson(UNIX和C语言的设计者之一,先景仰一下)设计了UTF-8编码。它的基本原理就是在0x80到0xff之间编码Unicode ,从而避免和ASCII冲突。具体的做法如下:

0 0000 0000 0000 0xxx xxxx -> 0xxxxxxx
0 0000 0000 0xxx xxxx xxxx -> 110xxxxx 10xxxxxx
0 0000 xxxx xxxx xxxx xxxx -> 1110xxxx 10xxxxxx 10xxxxxx
x xxxx xxxx xxxx xxxx xxxx -> 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

ASCII码在UTF-8中的编码完全不变,只要一个字节即可。对西方用户,这即完全兼容了以前的数据,也节省了储存空间。基本平面上的Unicode在UTF-8需要3个字节表示,所以对中文用户来说,稍微浪费了点。4字节的UTF-8可以表示21位数据,正好可以完全容纳全部的Unicode还多一点。

其实按这个思路,UTF-8可以使用7字节,甚至更多。最早的UTF-8设计为最多6字节,可以容纳31位的Unicode了。随着Unicode被限制到U+10ffff,在Unicode标准里也明确将UTF-8限制最多为4字节。

UTF-8的设计非常巧妙(大牛就是大牛),所以现在是最流行的编码,尤其在自由软件世界,因为不需要兼容早期的UCS-2,所以更是被广泛使用。

如果完全使用算法,UTF-8和Unicode之间并不是一一对应,同一个字符可以编码成多种UTF-8编码。比如字母“A”(0x41, 0100 0001)应该编码成01000001,但是两字节11000001 10000001按照算法也可以解码出字母“A”。这个问题如果在程序中不加注意,会引起严重的安全问题。所以Unicode标准严格规定了UTF-8编码中不允许出现的字节。

UTF-8是一种变长码,因此程序处理的难度要大一些。UTF-16虽然严格说也是变长,但是如果不关心辅助平面上的很少使用的字符,完全可以简单的当作两个字符处理,所以程序处理上可以简化。因此,在程序内部一般而言UTF-16更有优势。当然还要看具体应用的特点来选择内码。

Unicode在中国

GB2312、GBK、Big5

大陆的计算机编码最早是70年代末80年代初制定的GB2312,收录了6763个汉字,还有日文、俄文的相关符号。严格的说,GB2312是一个字符集,而不是编码方案。它用一个94x94的矩阵(选用这个大小的矩阵是为了符合ISO 2022标准)定义了一套汉字集合,汉字在矩阵中的坐标也就是常说的区位码。GB2312本身并没有没有定义汉字在计算机中的编码方式。真正的编码方案其实是EUC,它把两个1到 94的区位码各加上0xa0,就得到了计算机中使用的两个字节编码。不过现在我们通常也把这种方式叫做GB2312编码,虽然它其实和GB2312无关。

在GB2312的两字节汉字表示中,每个字节取值都肯定大于0x80,所以可以很好的兼容英文系统。但是这也带来一个比较大的缺陷,就是双字节表示汉字时无法区分第一字节和第二字节,因此如果一个字节错误,整行都无法正常解释。这也是为什么早年计算机上会出现“半个汉字”的问题。

当然GB2312最大的问题还是缺字,虽然说是覆盖了汉字99.75%的使用频率,但是日常应用还是显得捉襟见肘。当然这也有历史的原因,在80年代,处理几千个汉字已经很挑战计算机的处理能力了。所以当时汉字还分一、二级字库,大约3000 多个字符是一级字库,大多数系统都支持;剩下的二级字库就不一定都支持了,有些系统需要额外的磁盘,有些系统需要汉卡提供字库,有些甚至根本就不支持了。

不过GB2312有一个后续编码没有的好处,就是区位码基本按照拼音顺序排列,所以对内码排序就可以实现按拼音顺序排序,这个特点对早期的计算机和软件是很有利的。

因为GB2312缺字问题比较严重,其后陆陆续续又制定了一些国标,比如GB12345 ,对GB2312中未收录的汉字进行了补充。最后,我们熟悉的GBK出现了。GBK完全兼容GB2312,并扩充了大量非常用汉字,大约覆盖了两万多汉字。汉字覆盖上大致等于Unicode 2.1。

据传GBK是微软设计并顶着政府压力实施的,因为当时ISO 10646标准已经被政府接纳为GB13000,因此政府倾向于软件应该支持GB13000。但是微软为了兼容以前的产品,在简体版的95上还是坚持采用了内部UCS-2、外部GBK的方案。不过这只是传说,真实性不可考。

GBK的设计比较拙劣,它除了继承GB2312的缺点以外,还因为把第二字节的取值范围扩展到从0x40开始,造成了汉字编码的第二字节有可能和ASCII码冲突,在单字节处理的程序中会出现问题。一个例子就是TeX,“/”(0x5c)是有特别含义的控制字符,而GBK汉字中就有可能出现这个字节。不过好在这类汉字的频率非常低(常用汉字和GB2312编码相同,所以第二字节一定大于0x80),大多数TeX 用户甚至从未遇见过。这点比台湾用户要幸运的多,因为Big5码也有类似的情况,不过它出现的频率就相当高了。所以港台用户使用TeX必然需要先用预处理程序,不可能直接使用TeX编译文档。相同的情况也会出现在C语言里面。

不过由于微软在Windows 95简体版中实施了GBK方案(cp936),所以随着 Windows的流行GBK逐渐成为简体中文事实上的标准。GBK后来也被接纳为“技术规范”。平心而论,GBK已经完全足够覆盖日常应用的汉字了,除了罕见的人名。不过话说回来,现代人用乱七八糟的字取名字真是不可取。

台湾的情况就更复杂了,虽然好像只有一个Big5,但是因为政府没有强力推行标准,所以编码大多数情况下被大公司所左右,因此各种变种实在太多了。

Big5最早收录13053个汉字,也是两字节编码汉字,不过前后字节取值范围不同,这就避免了GB2312的缺陷。不过第二字节和GBK一样,和ASCII编码冲突,而且常用汉字也会出现这种冲突,所以Big5在计算机上应用真是相当成问题。另外,在Windows繁体版上实施的Big5(cp950)中不包括简体字和日文,所以台湾用户在和大陆、日本用户交流时也会相当麻烦。目前,台湾有民间人士在搞“Unicode 补全计划”(我觉得名字叫Big5补全计划似乎更合理),就是要将日文和部分简体字收录在Big5未使用的码位上,使得传统的Windows程序(主要是bbs软件)可以同时支持繁体、简体中文和日文。

总的来说,Big5除了覆盖的字符集比GB2312大以外,一无是处;和GBK比就更不值一提了。

GB13000

由于GB2312和Big5的缺陷,两岸对Unicode的接受程度都很高,基本上没有人反对使用Unicode。我还记得90年代初Unicode标准刚出来的时候,各个计算机报刊上一片欢腾和乐观的景象。这点不同于日本:在日本,反对Unicode的呼声还是有的。

大陆早在1993年就将ISO 10646.1:1993接纳为GB 13000.1-93国家标准。GB13000 完全等同于ISO 10646,不过因为只是推荐性标准,在当时被业界接受的程度不高。估计这也是后面的GB18030成为强制性国标的原因之一。

GB18030

GB18030是一个争议非常大的标准,很多人对它都有严重的误解。其实单纯从技术角度看,它还是说的过去的。

GB18030采用单字节、双字节、四字节编码。其中单字节和ASCII相同,双字节和 GB2312/GBK相同(事实上,GBK中若干字符的编码和GB18030并不相同,不过由于这些字符数量极少而且不常用,所以这个区别可以忽略)。使用四字节编码 Unicode中有但是GBK中没有的字符。

四字节编码中主要的两块是:

1. 第一字节从0x81到0x84,第二字节从0x30到0x39,第三字节从0x81到0xfe,第四字节从0x30到0x39,共50400个码位,覆盖单/双字节中没有包括的Unicode 基本多文种平面上的所有剩余码位,按顺序排列。BMP上最多可能的码位不到 65536(因为要扣掉扩展UTF-16占用的码位),双字节部分已经编码了超过两万的汉字,所以这块覆盖BMP绰绰有余。

2. 第一字节从0x90到0xe3,第二字节从0x30到0x39,第三字节从0x81到0xfe,第四字节从0x30到0x39,共1,058,400个码位,按顺序编码Unicode的16个辅助平面所有码位。16个平面总共码位为16*65536=1,048,576,因此这段覆盖辅助平面还有剩的。

除了这两段,GB18030的剩余码位是保留区和用户区。事实上,四字节编码的第一、三字节可以从0x81到0xfe,第二、四字节可以从0x30到0x39,因此四字节总的容量是126*10*126*10=1,587,600。加上双字节部分,所以GB18030号称总容量是一百六十万,大于Unicode的一百一十万。

GB18030于2000年推出,当时的Unicode版本为3.0,包括了2万多的汉字,所以宣传中常说GB18030编码了将近3万的汉字。这点造成了很多人误认为GB18030中只有3万汉字,远远少于后续Unicode中的7万多汉字。其实标准中对Unicode所有可能的码位都进行了编码,因此本质上等价于UTF-8/16/32,是一种Unicode编码方案,并不完全是字符集。只要Unicode新增的字符没有超出现有的17个平面,在 GB18030中早已有编码对应了。因此,很多人说Unicode 4.1新增的香港用字在 GB18030中没有,其实也是误解。而因为GB18030本身就是对Unicode进行编码,所以所谓GB18030阻碍了Unicode的发展,自然也就不能成立。

Unicode中所有的码位都可以唯一的映射到GB18030的编码,因此只要Unicode中存在的语言文字,GB18030都可以处理。所以很多人认为GB18030只是中文编码,不能处理多语言,这个看法也是不正确的。

GB18030只是一种编码方案,并不是字体标准或者字库标准。所以很多人抱怨政府制定了标准但是又不提供免费字体使用,或者抱怨现在没有GB18030编码的字体,其实并没有什么道理。不论哪种编码,使用字体都要通过一定的映射从字体文件里面获得图形信息,和外部使用的编码几乎没有关系。至于政府应该不应该提供免费字体,这就是另外一个问题了。

GB18030的最大优点是向下兼容GB2312/GBK,所以以前的中文数据不需任何转换就可以在支持GB18030的系统上处理。这似乎也是政府强制推行GB18030的最主要原因。强制推行的另一个原因可能和辅助平面支持有关:以微软为首的软件厂商一直使用UCS-2来处理Unicode,所以不愿意在产品中提供对辅助平面的支持。而中文应用是有可能使用到基本平面以外的Unicode的,所以强制执行GB18030使得软件商必须支持辅助平面。事实上微软也是因为需要支持GB18030才考虑把内部编码从UCS-2转换为UTF-16。另外国内软件厂商对Unicode的支持也不积极,强制执行GB18030对他们也是一个推动。

GB18030的设计思想和UTF-16非常接近,因此也具有UTF-16的优点:如果程序不需要四字节编码,可以简单的将其看作两个GBK字符,简化程序设计。不过由于四字节编码中包括了一些西方国家常用的字符,比如欧元符号,所以国际化程序不能不考虑四字节处理,这就使得这个优点打了很大的折扣。不过对中文用户设计中文处理程序,这个还是实实在在存在的优点,因为对中文(尤其是简体中文)应用,四字节编码几乎很少出现。

GB18030本身就是作为交换码设计的,因此编码中不会出现特殊控制字节,同时兼容ASCII编码,所以适合作为外部编码;再加上类似UTF-16的优点,所以也适合作为内码使用。如果是纯中文应用环境,不妨严肃的考虑使用GB18030设计软件,因为这样可以减少内外码转换的环节。不过由于现在软件设计都讲究国际化,同时CPU已经不在乎这点开销了,所以最终选择何种内码还是要权衡利弊的。

GB18030的另一个台面下的好处是它是由政府颁布的,因此修改或者新增自己需要的字符比较容易。而往Unicode里添加字符就需要层层的申请了,而且不一定成功。特别是Unicode中的汉字是中日韩统一汉字,因此如果和日本、韩国有不同意见,争论起来就旷日持久了。还有一些少数民族语言,支持起来也比加入 Unicode要容易的多。有人提过,藏文在Unicode里分配的码位就不够使用。

GB18030的毛病大多是从GBK身上继承过来的,比如第二、四字节和ASCII码冲突。四字节的编码也比一般编码处理起来困难些。前面也提过,UTF-16虽然也是四字节编码,不过不论处理哪种语言,四字节出现的概率都极小,而GB18030对中文以外的文字就不是这样了。不过总的说来,支持GB18030的难度绝对没有现在很多人认为的那么困难,只要程序支持通过UTF-8国际化,增加GB18030技术上就没有特别的困难,唯一的障碍在于是否有人愿意去写补丁,以及上游作者是否愿意接受这个补丁。在程序支持上比较突出的问题是一些老程序在不大改的前提下是无法支持这么大的字符集的,这些老程序中最著名的就是emacs。不过这个问题在任何一种Unicode编码上都存在,比如UTF-8,并不是GB18030特有的问题。

GB18030另一个问题是它实际上比Unicode更大,所以对它其中有但是Unicode中没有的字符怎么处理是一个问题。虽然现在还没有这一情况发生,但是未来版本无法排除这种情况的出现。

在我看来,GB18030最大的问题并不是在技术上,而是在标准的宣传上。主要的宣传仅仅针对普通用户,而不是技术人员,因此使用了大量不准确甚至错误的说法,再加上标准的原文不容易获得,所以在广大技术人员中产生了很多误解,造成了不少抵触情绪。所以即使大陆的从业人员在选择编码的时候也不愿意使用 GB18030,这不能不说是一个很大的失策。

总结

从Unicode的历史可以看到,一个标准的发展是很容易受到业界左右的。而软件厂商并不完全考虑用户的需要,特别是小众用户的需要。在中文用户影响还比较小的今天,由政府出面制定标准是很有必要的。从台湾Big5的发展也能看到反面的教训。我个人的看法,GB18030这种不见得有什么利益关系的标准,现在不是政府管的太多了,而是管的还不够,标准的推广和监督都还不够。

很多人认为最好是“直接使用”Unicode,或者ISO 10646,或者GB13000。但是 Unicode如果直接使用(也就是采取UCS-4/UTF-32编码),为了避免特殊字节造成问题,程序或者设备必须以32位为单位处理;即使采用UCS-2/UTF-16编码,也需要至少以16位为单位处理数据。换句话说,直接使用Unicode需要颠覆现在以字节为单位处理的传统。我觉得这几乎是不可能的事。所以,在可以预见的将来,软件内部使用一种编码,以方便程序处理;外部使用另一种兼容ASCII的编码输入输出,这种情况会一直存在。所以很多人认为GB18030是到GB13000/Unicode 的过渡编码,我却对这种观点持保留态度。不论Unicode多么完善,只要字节处理方式不变,就离不开GB18030/UTF-8这样的编码。

在国际化的趋势下,GB18030做为软件内码使用并不一定合适;而作为外部编码,前途如何现在还看不清。不过只要Windows还保持支持,那么还是可以看好的,至少不会惨淡收场。

最后,Unicode和编码技术只是软件的国际化和本地化的一部分,并不是全部,甚至不是最重要的一部分。指望外国作者支持UTF-8,或者本地使用UTF-8就可以实现中文化,或者能比较方便的实现中文话,是不切实际的。比如西方语言总的字符数很少,一般少于256,所以大量的程序设计时有这个默认的假设,看得见的比如Type 1字体文件、TeX的字体描述文件都只能支持256个字符,看不见的程序内部更多这种情况;即使没有硬性的限制,如果程序使用了O(n^2)复杂度的算法,对西方语言来说n常常是100出头,性能上是可以接受的,但是对东方语言, n常常都是几万,也许会造成软件实际上几乎无法使用。还有语言文法上的差异,比如西方文字可以在空格处断行,而中文并没有空格来提示合法的断行位置,这个在显示和排版上也是大问题。总之,Unicode解决了软件国际化的一个问题,剩下的问题还需要大量的工作。

参考文献

1. Universal Character Set, http://en.wikipedia.org/wiki/Universal_Character_Set

2. Unicode, http://en.wikipedia.org/wiki/Unicode

3. 中日韩统一表意汉字, http://zh.wikipedia.org/wiki/%E4%B8%AD%E6%97%A5%E9%9F%93%E7%B5%B1%E4%B8%80%E8%A1%A8%E6%84%8F%E6%96%87%E5%AD%97

原创粉丝点击