Unicode 的多种表现形式(摘录)
Unicode 的多种表现形式Mark Davis
IBM 的开发人员,Unicode 联盟主席
1999 年 9 月
起初,Unicode 是一种简单的、固定长度的 16 位编码。按照它最初的设计原则,16 位便足以容纳所有的现代书写系统。但随着 Unicode 的增长和发展,这些原则不得不被废弃。为保证与旧字符集的兼容性而添加的字符占用了大量的可用空间。其中许多兼容性字符都是冗余的,之所以保留它们只是因为当时不同的平台技术还无法按最初的设计表示这些字符。
因此 16 位已不能满足需要。Unicode 需要一种扩展机制,以便容纳更多的字符。标准机制使用称为 surrogate 的 Unicode 值对为 1,000,000 多个可能的值定址。目前 surrogate 空间尚无字符,不过到 2000 年末就应该有了。
此外,某些系统难以扩展它们的接口使其用 16 位单元进行处理。这些系统需要一种能够按 8 位来处理的 Unicode 形式。另一些系统则很容易使用 32 位的更大单元来表示 Unicode。
为满足这些不同的需求,Unicode 现在有三种表现形式:UTF-8、UTF-16 和 UTF-32。(请参阅本文后面的首字母缩写词定义。)这三种形式的开发融汇了大量的智慧,每种形式对于其特定的环境都是最实用的,同时在需要时这三种形式还可以快速地相互转换。理解每种形式的功能和局限性是很重要的。
字符与字形
在深入研究 Unicode 的这些形式之前,我们需要澄清几点。第一点是字符与字形的区别。字形是表示一个字符或一个字符的局部的一种特殊图像。字形的形状可能相差很大:下面只是字母 a 的可能字形中的一部分。在下面的示例中,表格中分别列出了各种不同的选择。
字符 字形样例
字形与字符并不是一一对应的。例如,f 后跟 i 的字符序列可用一种称为 fi 连字的单个字形表示。请注意,它们的外形结合在一起,并且 i 的点也不见了。
字符序列 字形样例
另一方面,与 fi 连字相同的图像也可以通过具有正确外形的两个字形获得。选择使用单个字形还是使用两个字形的序列由包含这些字形的字体和绘制软件决定。
字符序列 可能的字形序列
与此类似,带重音符号的字符可以用单个字形表示,也可以用不同的组件字形适当安放而成。此外,单独的重音符号本身也可以被看作是字符,在这种情况下,一个字符序列也可能对应不同的字形表示:
字符序列 可能的字形序列
在非拉丁语言中,字形和字母之间的联系可能更不直接。某些字形可能需要根据周围的其他字形改变它的外形和宽度,这些字形被称为环境形式。例如,请看下面的阿拉伯字形。
字符 取决于环境的可能字形
为了对齐,字形可能需要加宽,而不是简单增加间隔的宽度。理论上,这将牵涉到根据所需的宽度改变字形的外形。在某些系统中,这种加宽可以通过插入称为 kashidas 的附加连接字形实现。可以想象,在这种情况下单个字符可能对应 kashidas + 若干字形 + kashidas 这样的序列。
字符 字形序列
在另一些情况下,单个字符必须对应两个字形,因为这两个字形位于其他字母的 两侧。请看下面的泰米尔字符。如果这些字形中的某个字形与其他字符组成一个连字,则我们将遇到这样一种情况:即其中一个字符的 局部与一个字形的局部相对应。如果一个字符(或其任一部分)与一个字形(或其任一部分)相对应,则我们说该字符对该字形 有贡献。
字符 拆分字形
结果是,字形与字符之间并不是一对一的关系,并且也不能根据文本来推算。由于存在诸如阿拉伯语和希伯来语这样的从右到左的文字,所以字形的排序通常也不会与字符的排序对应。一个特定的字符串是否由特定的字形序列绘制将取决于主机的操作系统和字体的复杂程度。
字符、代码点与代码单元
原则上,Unicode 的目标十分简单:为世界上的每个字符分配一个数字,称为代码点(或 纯量值)。这些数字的的范围是从 0 到 1,114,111 = 10FFFF16,不过形式为 xxFFFE16 或 xxFFFF16 的每个值都是非法的。
给定这个目标,我们就可以说 Unicode 是对字符的编码,对吗?
不对。Unicode 的一个主要设计目标就是与原有的字符编码标准相互配合。这要求无损的相互转换,也就是要求既可以将文本从一种编码转换为 Unicode,也可以作相反的转换,而不会丢失信息。不幸的是,那些旧的字符编码标准包含了大量非字符的内容,Unicode 不得不将这些内容统统包括在内。
这些非字符的内容包括连字字形、环境形式的字形、变宽字形、字符序列和装饰字形(如带圈的数字)。以下示例说明了 Unicode 在什么情况下将字形作为单个字符编码。与字形一样,字符与代码点之间也不是一对一的关系。最终用户认为的单个字符( 语义图)实际上可能是由多个代码点表示的;反过来,单个代码点可能对应多个字符。下面是几个示例。
字符 Unicode 代码点 注释
每个阿拉伯环境形式字形都有一个代码点。
这个连字字形有一个代码点。
单个代码点表示三个字符的一个序列。
梵文字母音节 ksha 由三个代码点表示。
即使我们有了代码点,事情也尚未完成。特定的编码会将代码点表示为包含一个或多个 代码单元的一个序列,其中一个代码单元就是一个内存单元。内存单元可能是8 位、16 位或 32 位。在内存格式部分我们将进一步讨论这个问题。
避免多义性
我们已经看到,字符、字形、代码点和代码单元各不相同。不幸的是,术语 字符已大大超载了。在不同的情况下人们用它来表达以下各种不同的意思:
纸上的一幅图像(字形)
最终用户认为是一个字符的东西(语义图)
字符编码标准编码的东西(代码点)
字符编码中的内存存储单元(代码单元)
由于这个原因(似乎有点讽刺意味),在讨论字符编码时最好完全避免使用术语 字符,而坚持使用代码点。
内存格式
我们已经了解了所有特殊情况,让我们回头继续讨论 Unicode 格式。我们暂时将只讨论内存中 UTF 的使用(当涉及到序列化时,情况将会更复杂,我们将在以后讨论那种情况)。每种 UTF-n 都将一个代码点表示为一个或多个代码单元的一个序列,其中每个代码单元占 n 位。
表 1 显示了 UTF 所使用的代码单元格式,并指出了所有计算机文本的平均存储需求。UTF-16 和 UTF-32 的平均存储需求应该不随时间变化(如果 UTF-16 surrogate 空间字符的平均数能达到 0.1%,就够让人感到吃惊了)。
但是,因为 UTF-8 在很大程度上依赖于不同语言中的文本比例,所以它的存储需求是变化的。从 1999 年到 2004 年之间的增长寄希望于南亚和东亚地区计算机使用量的增加。(表中的估计值是将全球的网页语言当作全部计算机文本得到的替代值。这些数字取自 IDC 的评估。)
表 1:UTF 类型
UTF 格式 每页(3000 个字符)所需平均存储估计
UTF-8
3 KB
(1999)
--------------------------------------------------------------------------------
5 KB
(2003) 平均而言,英语的每个代码点占用一个单元稍多一点。大多数拉丁文字语言大约占用 1.1 字节。希腊语、俄语、阿拉伯语和希伯来语大约占用 1.7 字节,大多数其他语言(包括日语、中文、朝鲜语和北印度语)占用 3 个字节。surrogate 空间中的字符占用 4 个字节,但这些字符在全世界文本中所占的比例是非常小的。
UTF-16
6 KB 所有现代书写系统所用的大多数通用字符已用 2 个字节表示。surrogate 空间中的字符占用 4 个字节,但它们在全世界文本中所占的比例是非常小的。
UTF-32
12 KB 全部占用 4 个字节
使用 UTF-8 和 UTF-16 编程比使用其他混合宽度字符编码更直接。对于每个代码点,它们要么是一个单元素形式,要么是一个多单元形式。对于 UTF-16 而言,则只有一个多单元形式,恰恰具有两个代码单元。对于 UTF-8 而言,尾随单元数由引导单元的值决定:这样相同的引导单元就不能有不同的尾随单元数。在每个编码形式内部,单元素、引导单元和尾随单元的值都是 完全分开的。这对程序的实现有极重要的意义:
没有重叠。如果您在字符串 B 中搜索字符串 A,则永远不会获得代码点的一个假匹配。您也永远不需要为进行字符串搜索而转换代码点。因为一个序列的结尾永远不会与另一个序列的开头相同,所以永远不会发生假匹配。
重叠是通常的多字节编码(如 Shift-JIS)最严重的问题之一。而所有 UTF 则都避免了这个问题。
确定的边界。如果随机访问文本,您总能够使用很少的机器指令确定最近的代码点边界。
越过 (Pass-through)。如果处理过程不查看特定字符值,则不需要知道文本的内部结构。
简单迭代。获取下一个或上一个代码点非常直接,只需很少的机器指令。
检索较慢。 除在 UTF-32 中之外,查找与第 n 个代码点相应的代码单元边界,或者查找包含第 n 个代码单元的代码偏移量效率不高。这两项操作都涉及从文本开头开始的扫描。
频率。 因为全球文本中需要 surrogate 空间的文本所占的比例非常小,所以 UTF-16 代码总是对单个代码单元进行优化。对于 UTF-8 而言,可能也值得对单个单元的情况进行优化,但如果它使多单元情况下的速度大幅下降,就不值得这样做了。
UTF-8 的复杂性还有一个方面,称为最短形式需求。对于表 1 中可以表示代码点的可能序列,Unicode 标准要求生成最短的序列。但是,当从代码单元映射回代码点时,执行时并不需要检查最短形式。而在 UTF-16 中这个问题不会发生。
今后一两年大多数系统将升级它们的 UTF-16 以支持 surrogate。这种升级可以分阶段实施。从市场的观点来看,近期令人感兴趣的唯一一种 surrogate 空间字符可能是 CJK 象形文字的补充集,用于日语、中文和朝鲜语。如果一个系统已经进行了国际化,则系统上的大多数操作将工作得相当好,只需做很少的修改就能满足近期的这些需求。
存储与性能
当对全球计算机中的文本作平均时,UTF-8 和 UTF-16 都比 UTF-32 要密集得多。平均而言,UTF-8 目前比 UTF-16 还要密集,尽管它不是特别适合东亚文字,因为后者的每个代码点大约占 3 个字节。随着时间的推移,UTF-8 可能将逐渐向 UTF-16 靠近,并且随着计算机继续进军东亚和南亚地区,其平均密集度可能也将越来越小。在存储需求方面,UTF-8 和 UTF-16 比 UTF-32 有很大的优势。
如果使用 UTF-32,则代码点定界、迭代和索引都非常快。对于 UTF-16 而言,代码点定界、访问给定偏移地址处的代码点、迭代都要涉及一些额外的机器指令;UTF-8 更麻烦。对于这两种情况,索引都比较慢,但实践中很少需要按不同的代码单元进行索引,除非要与使用 UTF-32 代码单元的规范(如 XSL)通信。
除非处理字符串的 API 只允许通过代码点偏移量访问,否则有关索引的这个结论是非常正确的。这种设计的效率是非常低的:字符串应该总是允许使用 代码单元偏移量进行的索引。此外,因为代码点与最终用户对字符的期望通常不能达到一致,所以使用 语义图(用户字符)定界并用字符串(而不是按单个代码点)存储文本通常要好得多。有关详细信息,请参阅 Unicode 标准,版本 3.0。
不同 UTF 之间的转换非常快。与旧编码(如 Latin-2)之间的转换不同,UTF 之间的转换不需要表查找。
整体性能也可能受其他因素的影响。例如,如果代码单元与机器字长相同,则访问更快;但如果它们占用更多的内存,则可能发生更多的页失效和高速缓存失效,从而降低了性能。
序列化格式
序列化是将一个代码单元序列转换为一个字节序列进行存储或传输的过程。序列化有两个复杂因素, endianness 和编码标记:
Endianness。如果编码单元不是单个字节,则随机器体系结构的不同,可以按两种方式写入:big endian(最重要的字节在先),或者 little endian(最不重要的字节在先)。对于现在的微处理器速度而言,这不是什么大问题,但在 Unicode 当初被采用的时候,BE 和 LE 两种格式还是需要的。
编码。如果系统不用字符编码标记文件,则系统可能知道该文件包含文本,但不知道该文件使用的是哪种编码。
为了满足这两个要求(出自一家无名但相当有影响的公司),字符 ZERO WIDTH NOBREAK SPACE (FEFF16) 可用作文件开头几个字节中的标记。当此字符用于这一目的时,它就被称为 字节次序标记 (BOM)。BOM 有一个特殊的特性,即它的字节交换副本 BSBOM (FFFE) 被定义为永远不是合法的 Unicode 字符,所以它也用来表明 endianness。此标记不是内容的一部分 — 将其看作一个小标头 — 在处理时必须去掉它。例如,盲目地合并两个文件将得到不正确的结果。
因为这个原因,按照 endianness 及是否使用标记,UTF 有多种形式(有关详细信息,请参阅表 2)。重要的是不要混淆内存格式和序列化格式,尤其不要因为它们中有一部分具有相同的名称就将它们混淆。例如,当 UTF-16 指内存格式时,就不存在 endianness 问题,并且也不使用标记(当生成或读取文件时可能存在一个标记,但这只应该发生在很短的一段时间内)。另一方面,当 UTF-16 指序列化格式时,则它可能有一个标记,并可能使用任一种 endianness。
现在,大多数人永远不需要知道这些编码的全部琐碎细节,但表 2 为那些对此感兴趣的人说明了这些区别。在实践中,这些细节大部分都由字符编码转换实用工具透明地处理。
表 2:UTF 的序列化
UTF-8 最开头的 EF BB BF 是一个标记,表明文件的其余部分是 UTF-8。
任何 EF BF BE 都是错误。
文件开头的真正的 ZWNBSP 首先需要一个标记。
UTF-8N 全部文本都是正常的 UTF-8,没有标记。
开头的 EF BB BF 是 ZWNBSP。
任何 EF BF BE 都是错误。
UTF-16 开头的 FE FF 是一个标记,表明文本的其余部分是 big endian UTF-16。
开头的 FF FE 是一个标记,表示文本的其余部分是 little endian UTF-16。
如果这两个标记都不存在,则全部文本都是 big endian。
文件开头的真正的 ZWNBSP 首先需要一个标记。
UTF-16BE 全部文本都是 big endian:没有标记。
开头的 FE FF 是 ZWNBSP。
任何 FF FE 都是错误的。
UTF-16LE 全部文本都是 little endian:没有标记。
开头的 FF FE 是 ZWNBSP。
任何 FE FF 都是错误。
UTF-32 开头的 00 00 FE FF 是一个标记,表明文本的其余部分是 big endian UTF-32。
开头的 FF FE 00 00 是一个标记,表明文本的其余部分是 little endian UTF-32。
如果这两个标记都不存在,则全部文本都是 big endian。
文件开头的真正的 ZWNBSP 首先需要一个标记。
UTF-32BE 全部文本都是 big endian:没有标记。
开头的 00 00 FE FF 是 ZWNBSP。
任何 FF FE 00 00 都是错误。
UTF-32LE 全部文本都是 little endian:没有标记。
开头的 FF FE 00 00 是 ZWNBSP。
开头的 00 00 FE FF 是错误。
注:用斜体表示的名称尚未注册,但作为参考很有用。
决策,决策...
最后,选择使用哪种编码格式在很大程度上将依赖于程序设计环境。对于目前只提供 8 位字符串、但支持多字节的系统,UTF-8 可能是最佳选择。对于无需考虑存储需求的的系统而言,UTF-32 可能最好。对于已经使用 UTF-16 字符串的系统(如 Windows、Java 或 ICU)而言,UTF-16 是显而易见的选择。即使它们尚未升级到完全支持 surrogate,它们也很快会升级。
如果程序设计环境不成问题,综合考虑简洁、性能和存储各方面,则建议使用 UTF-16。
已定义的首字母缩写词
对于不熟悉本文中所使用的某些首字母缩写词的读者,下面是一个简短的列表。
ICU IBM Classes for Unicode:IBM 推出的支持 Unicode 的开放源码软件
IDC 国际数据公司:一个信息技术数据供应商
UTF Unicode 变换格式:将每个 Unicode 代码点映射为唯一的代码单元序列的变换。
注:UTF-32 是来自 UTR #19: Interoperable 32-bit Serialization 的工作名称。除了它被限制为有效的 Unicode 值以支持交互操作性以外,它与 ISO 10646 格式 UCS-4 非常相似。Unicode 标准同样限制 UTF-8 的 Unicode 定义。
W3C 万维网联盟:负责 HTML、XML 和其他 Web 标准的组织。
XML 可扩充的标记语言:"Web 上结构化文档和数据的通用格式"
XSL 可扩充的样式表语言:由 W3C 开发的 XML 系列中的众多规范之一。
作者简介
Mark Davis 博士是负责国际软件体系结构的高级技术人员。Mark 参与了建立 Unicode 的工作,并且是 Unicode 联盟的主席。他还是 Unicode 标准 1.0 版本和 2.0 版本的主要作者之一。在不同时期,他所在的部门曾包括了多个软件组,涉及的领域包括文本、国际化、操作系统服务、Windows 接口和技术通信。技术上,他专门研究面向对象的编程以及国际化和文本软件的体系结构的实现。
还是做个连接把,图无法显示
http://www-900.ibm.com/developerWorks/cn/unicode/utfencodingforms/index.shtml
页:
[1]