2.4. 支持世界上几乎所有字符的字符编码:Unicode

好了,介绍完了ISO/IEC 8859的种种,这下可以开始介绍Unicode了。

前面已经提到了,由于随着计算机的发展,自然会发展到亚洲各国和其他一些地方,然后这些国家也遇到同样问题,即如何把自己的国家的字符,显示到对应的屏幕上。

2.4.1. Unicode和ISO 10646的关系

Unicode这个词的中文翻译,有译为万国码,单一码,标准万国码,但是最常见的翻译还是统一码。

2.4.1.1. ISO 10646=UCS

国际标准组织ISO,定义了对应的编码标准ISO/IEC 10646,简称为ISO 10646,此标准所定义的字符集,称作为通用字符集(Universal Character Set,UCS)。

并不是所有的系统都需要支持像組合字符这样的的先进机制。

因此ISO 10646指定了如下三种实现级别:

  1. 级别1:不支持组合字符和諺文字母字符。
  2. 级别2:类似于级别1,但在某些文字中,允许一列固定的组合字符,因为如果没有最起码的几个组合字符,UCS就不能完整地表达这些语言。
  3. 级别3:支持所有的通用字符集字符,如,可以在任意一个字符上加上一个箭头或一个鼻音化符號

即,对于多数的实际使用中,并不一样要求你实现包括世界上所有的字符,那就不一定非的要实现对应的第三级别,很多时候只需要实现第一级别就足够涵盖平常所用到的大部分的字符了。

我们平时会看到UCS-2,UCS-4,就是对应的ISO 10646标准中所定义的,对应的用2个字节或4个字节去表示同一个字符。

2.4.1.2. Unicode 和ISO 10646的联系

历史上存在两个独立的尝试创立单一字符集的组织,即国际标准化组织(ISO)和多语言软件制造商组成的统一码联盟。

前者开发的 ISO/IEC 10646 项目,后者开发的Unicode项目。

因此最初制定了不同的标准。

1991年前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码;ISO也承诺,ISO 10646将不会超出U+10FFFF的UCS-4编码赋值,以使得两者保持一致。

两个项目仍都存在,并独立地公布各自的标准,但统一码联盟和ISO/IEC JTC1/SC2都同意保持两者标准的码表兼容,并紧密配合以保证之后的扩展也一致。

其各自的标准之间的对应关系如下:

表 2.3. ISO/IEC 10646与Unicode的版本对应关系

ISO/IEC 10646版本 Unicode版本
ISO/IEC 10646-1:1993 Unicode 1.1
ISO/IEC 10646-1:2000 Unicode 3.0
ISO/IEC 10646-2:2001 Unicode 3.2
ISO/IEC 10646:2003 Unicode 4.0
ISO/IEC 10646:2003 plus Amendment 1 Unicode 4.1
ISO/IEC 10646:2003 plus Amendment 1, Amendment 2, and part of Amendment 3 Unicode 5.0
ISO/IEC 10646:2003 plus Amendments 1 to 4 Unicode 5.1
ISO/IEC 10646:2003 plus Amendments 1 to 6 Unicode 5.2
ISO/IEC 10646:2011 Unicode 6.0

2.4.1.3. Unicode和ISO 10646的区别

统一码联盟公布的Unicode标准包含了ISO/IEC 10646-1实现级别3的基本多文种平面BMP。在两个标准里,所有的字符都在相同的位置并且有相同的名字。

ISO/IEC 10646标准,就像ISO/IEC 8859标准一样,只不过是一个简单的字符集表。它定义了一些编码的别名,指定了一些与标准有关的术语,并包括了规范说明,指定了怎样使用UCS连接其他ISO标准的实现,比如ISO/IEC 6429和ISO/IEC 2022。还有一些与ISO紧密相关的,比如ISO/IEC 14651是关于UCS字符串排序的。

Unicode标准,额外定义了许多与字符有关的语义符号学。Unicode详细说明了绘制某些语言(如阿拉伯语)表达形式的算法,处理双向文字(比如拉丁文和希伯来文的混合文字)的算法,排序与字符串比较所需的算法,等等。

所以,可以理解为,ISO 10646中定义了编码规则,定义了哪些值对应了哪些字符,而Unicode不仅定义了这些编码规则,还定义了其他一些关于文字处理的细节算法等内容。

即:

Unicode

= ISO 10646的编码规则 + 某些语言的细节处理算法

对于一般人来说,Unicode 和 ISO 10646,虽然两者有些细节的区别,但是我们多数不用去关系这点细节内容,而对于字符编码规则方面,此处可以简单的理解为:

Unicode

= ISO 10646编码标准

= 标准所制定的UCS字符集

2.4.2. Unicode编码规则

为了将世界上几乎所有的字符都涵盖了,那么就要了解世界上,有哪些字符。

除了之前ASCII的拉丁字母,ISO 8859所包含的欧洲多国用的字符之外,亚洲一些国家,包括中文,日文,韩文等,尤其是中文,包含的字符数,大概有几万个。

因此,Unicode的编码就要设计的把这么多的字符都包含在内。

Unicode的编码方式与上面提到的ISO 10646的UCS概念相对应,目前实际应用的Unicode版本对应于UCS-2,即2字节的UCS字符集,使用16位的编码空间。每个字符占用2个字节,这样理论上一共最多可以表示2^16=65536个字符。基本满足各种语言的使用。实际上目前版本的Unicode尚未填充满这16位编码,保留了大量空间作为特殊使用或将来扩展。

上述16位Unicode字符构成基本多文种平面(Basic Multilingual Plane,简称BMP)。最新(但未实际广泛使用)的Unicode版本定义了16个辅助平面,两者合起来至少需要占据21位的编码空间,比3字节略少。但事实上辅助平面字符仍然占用4字节编码空间,与UCS-4保持一致。未来版本会扩充到ISO 10646-1实现级别3,即涵盖UCS-4的所有字符。

UCS-4是一个更大的尚未填充完全的31位字符集,加上恒为0的首位,共需占据32位,即4字节。理论上最多能表示2^31=2147483648=21亿左右个字符,完全可以涵盖一切语言所用的符号。

具体的取值范围和所对应的平面空间划分,参见图 2.3 “Unicode中的各种平面划分”

图 2.3. Unicode中的各种平面划分

Unicode中的各种平面划分

Unicode中的0-0xFFF的BMP中的任何一个编码的值,称为码点(Code Point),对应用U+hhhh来表示,其中每个h 代表一个十六进制数位。

与UCS-2编码完全相同。对应的4字节UCS-4编码后两个字节一致,前两个字节的所有位均为0。

2.4.3. Unicode字符编码所对应的存储和交换标准:UTF-8, UTF-16, UTF-32

需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

比如,汉字“严”的Unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

这里就有两个严重的问题,第一个问题是,如何才能区别Unicode和ASCII?计算机怎么知道三个字节表示一个Unicode中的字符,而不是分别表示三个ASCII的 字符呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

它们造成的结果是:

  1. 出现了Unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示Unicode
  2. Unicode在很长一段时间内无法推广,直到互联网的出现

2.4.3.1. UTF-8

互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种Unicode的实现方式。其他实现方式还包括UTF-16和UTF-32,不过在互联网上基本不用。

重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8的编码规则很简单,只有二条:

  • 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的Unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的
  • 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的Unicode码

下表总结了编码规则,字母x表示可用编码的位。

表 2.4. Unicode与UTF-8之间的编码映射关系

Unicode符号范围(十六进制) UTF-8编码方式(二进制)
0000 0000-0000 007F 0xxxxxxx
0000 0080-0000 07FF 110xxxxx 10xxxxxx
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

下面,还是以汉字“严”为例,演示如何实现UTF-8编码。

已知“严”的Unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此“严”的UTF-8编码需要三个字节,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,“严”的UTF-8编码是“11100100 10111000 10100101”,转换成十六进制就是E4B8A5。

2.4.3.2. Unicode与UTF-8之间的转换

通过上一节的例子,可以看到“严”的Unicode码是4E25,UTF-8编码是E4B8A5,两者是不一样的。它们之间的转换可以通过程序实现。

在Windows平台下,有一个最简单的转化方法,就是使用内置的记事本小程序Notepad.exe。打开文件后,点击"文件""另存为"会跳出一个对话框,在最底部有一个"编码"的下拉条:

图 2.4. Notepad中的各种编码

Notepad中的各种编码

里面有四个选项:ANSI,Unicode,Unicode big Endian 和 UTF-8:

  • ANSI是默认的编码方式。对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对Windows简体中文版,如果是繁体中文版会采用Big5码)。
  • Unicode编码指的是UCS-2编码方式,即直接用两个字节存入字符的Unicode码。这个选项用的Little Endian格式
  • Unicode Big Endian编码与上一个选项相对应

    关于Little Endian和Big Endian,可以参考大端(Big Endian)与小端(Little Endian)详解

  • UTF-8编码,也就是上一节谈到的编码方法

选择完”编码方式“后,点击”保存“按钮,文件的编码方式就立刻转换好了。

下面,举一个实例:

打开”记事本“程序Notepad.exe,新建一个文本文件,内容就是一个”严“字,依次采用ANSI,Unicode,Unicode big Endian 和 UTF-8编码方式保存。

然后,用文本编辑软件UltraEdit的”十六进制功能“,观察该文件的内部编码方式。

  • ANSI

    文件的编码就是两个字节“D1 CF”,这正是“严”的GB2312编码,这也暗示GB2312是采用大头方式存储的

  • Unicode

    编码是四个字节“FF FE 25 4E”,其中“FF FE”表明是小头方式存储,真正的编码是4E25。

  • Unicode big Endian

    编码是四个字节“FE FF 4E 25”,其中“FE FF”表明是大头方式存储。

  • UTF-8

    编码是六个字节“EF BB BF E4 B8 A5”,前三个字节“EF BB BF”表示这是UTF-8编码,后三个“E4B8A5”就是“严”的具体编码,它的存储顺序与编码顺序是一致的

2.4.3.2.1. 关于UTF-8的BOM:“EF BB BF”

对于UTF-8的BOM(Byte Order Mark),即“EF BB BF”,是对于UTF-8编码,微软自己添加的,由此,会导致和其他很多软件等不兼容。而Unicode标准中,也不推荐此给UTF-8添加“EF BB BF”的BOM。

刚去测试了一下,在Window XP中,将中文汉字“严”在记事本中另存为UTF-8之后,用Notepad++去查看其十六进制的值,的确是“EF BB BF E4 B8 A5”,然后手动删除了“EF BB BF”的BOM,保存后,再去用记事本打开,发现没了BOM的UTF-8,记事本也是可以正确显示出“严”字的。所以,结论是:

  1. 给UTF-8加“EF BB BF”的BOM,是微软自己的做法,即微软发现编码是UTF-8的话,会给文件最开始加上“EF BB BF”
  2. Unicode的官方标准,不推荐这种做法,即不推荐给UTF-8加“EF BB BF”的BOM
  3. 所以,其他人写软件处理文字编码的话,最好不要给UTF-8加BOM。当然,如果你非得要兼容微软的做法,那么去解析不同编码的文件的话,针对UTF-8编码,就要考虑这个特殊的的BOM了

更多关于BOM的解释,参见第 2.7 节 “BOM”