[转]Java 平台中的增补字符
本文转自:http://java.sun.com/developer/technicalArticles/Intl/Supplementary/index_zh_CN.html
Java 平台中的增补字符
2004 年 5 月 English: Supplementary Characters in the Java Platform
摘要本文介绍 Java 平台支持增补字符的方式。增补字符是 Unicode 标准中代码点超出 U+FFFF 的字符,因此它们无法在 Java 编程语言中描述为单个的 16 位实体(例如 Java 平台目前正在改进,以便支持对增补字符的处理,这种改进对现有的应用程序影响微乎其微。新的低层 API 在需要时能够使用单个的字符运行。不过,大多数文本处理 API 均使用字符序列,例如 除详细解释这些改进之外,本文同时为应用程序开发人员确定和实现必要的更改提供指导,以支持整个 Unicode 字符集的使用。 背景Unicode 最初设计是作为一种固定宽度的 16 位字符编码。在 Java 编程语言中,基本数据类型 对增补字符的支持也可能会成为东亚市场的一个普遍商业要求。*应用程序会需要这些增补字符,以正确表示一些包含罕见中文字符的姓名。出版应用程序可能会需要这些增补字符,以表示所有的古代字符和变体字符。中国*要求支持 GB18030(一种对整个 Unicode 字符集进行编码的字符编码标准),因此,如果是 Unicode 3.1 版或更新版本,则将包括增补字符。*标准 CNS-11643 包含的许多字符在 Unicode 3.1 中列为增补字符。香港*定义了一种针对粤语的字符集,其中的一些字符是 Unicode 中的增补字符。最后,日本的一些供应商正计划利用增补字符空间中大量的专用空间收入 50,000 多个日文汉字字符变体,以便从其专有系统迁移至基于 Java 平台的解决方案。 因此,Java 平台不仅需要支持增补字符,而且必须使应用程序能够方便地做到这一点。由于增补字符打破了 Java 编程语言的基础设计构想,而且可能要求对编程模型进行根本性的修改,因此,Java Community Process 召集了一个专家组,以期找到一个适当的解决方案。该小组被称为 JSR-204 专家组,使用 Unicode 增补字符支持的 Java 技术规范请求 的编号。从技术上来说,该专家组的决定仅适用于 J2SE 平台,但是由于 Java 2 平台企业版 (J2EE) 处于 J2SE 平台的最上层,因此它可以直接受益,我们期望 Java 2 平台袖珍版 (J2ME) 的配置也采用相同的设计方法。 不过,在了解 JSR-204 专家组确定的解决方案之前,我们需要先理解一些术语。 代码点、字符编码方案、UTF-16:这些是指什么?不幸的是,引入增补字符使字符模型变得更加复杂了。在过去,我们可以简单地说“字符”,在一个基于 Unicode 的环境(例如 Java 平台)中,假定字符有 16 位,而现在我们需要更多的术语。我们会尽量介绍得相对简单一些 — 如需了解所有详细的讨论信息,您可以阅读 Unicode 标准第 2 章 或 Unicode 技术报告 17“字符编码模型 ”。Unicode 专业人士可略过所有介绍直接参阅本部分中的最后定义。 字符是抽象的最小文本单位。它没有固定的形状(可能是一个字形),而且没有值。“A”是一个字符,“€”(德国、法国和许多其他欧洲国家通用货币的标志)也是一个字符。 字符集是字符的集合。例如,汉字字符是中国人最先发明的字符,在中文、日文、韩文和越南文的书写中使用。 编码字符集是一个字符集,它为每一个字符分配一个唯一数字。Unicode 标准的核心是一个编码字符集,字母“A”的编码为 004116 和字符“€”的编码为 20AC16 。Unicode 标准始终使用十六进制数字,而且在书写时在前面加上前缀“U+”,所以“A”的编码书写为“U+0041”。 代码点是指可用于编码字符集的数字。编码字符集定义一个有效的代码点范围,但是并不一定将字符分配给所有这些代码点。有效的 Unicode 代码点范围是 U+0000 至 U+10FFFF。Unicode 4.0 将字符分配给一百多万个代码点中的 96,382 代码点。 增补字符是代码点在 U+10000 至 U+10FFFF 范围之间的字符,也就是那些使用原始的 Unicode 的 16 位设计无法表示的字符。从 U+0000 至 U+FFFF 之间的字符集有时候被称为基本多语言面 (BMP)。因此,每一个 Unicode 字符要么属于 BMP,要么属于增补字符。 字符编码方案是从一个或多个编码字符集到一个或多个固定宽度代码单元序列的映射。最常用的代码单元是字节,但是 16 位或 32 位整数也可用于内部处理。UTF-32、UTF-16 和 UTF-8 是 Unicode 标准的编码字符集的字符编码方案。 UTF-32 即将每一个 Unicode 代码点表示为相同值的 32 位整数。很明显,它是内部处理最方便的表达方式,但是,如果作为一般字符串表达方式,则要消耗更多的内存。 UTF-16 使用一个或两个未分配的 16 位代码单元的序列对 Unicode 代码点进行编码。值 U+0000 至 U+FFFF 编码为一个相同值的 16 位单元。增补字符编码为两个代码单元,第一个单元来自于高代理范围(U+D800 至 U+DBFF),第二个单元来自于低代理范围(U+DC00 至 U+DFFF)。这在概念上可能看起来类似于多字节编码,但是其中有一个重要区别:值 U+D800 至 U+DFFF 保留用于 UTF-16;没有这些值分配字符作为代码点。这意味着,对于一个字符串中的每个单独的代码单元,软件可以识别是否该代码单元表示某个单单元字符,或者是否该代码单元是某个双单元字符的第一个或第二单元。这相当于某些传统的多字节字符编码来说是一个显著的改进,在传统的多字节字符编码中,字节值 0x41 既可能表示字母“A”,也可能是一个双字节字符的第二个字节。 UTF-8 使用一至四个字节的序列对编码 Unicode 代码点进行编码。U+0000 至 U+007F 使用一个字节编码,U+0080 至 U+07FF 使用两个字节,U+0800 至 U+FFFF 使用三个字节,而 U+10000 至 U+10FFFF 使用四个字节。UTF-8 设计原理为:字节值 0x00 至 0x7F 始终表示代码点 U+0000 至 U+007F(Basic Latin 字符子集,它对应 ASCII 字符集)。这些字节值永远不会表示其他代码点,这一特性使 UTF-8 可以很方便地在软件中将特殊的含义赋予某些 ASCII 字符。 下表所示为几个字符不同表达方式的比较:
另外,本文在许多地方使用术语字符序列或 这么多术语。它们与在 Java 平台中支持增补字符有什么关系呢? Java 平台中增补字符的设计方法JSR-204 专家组必须作出的主要决定是如何在 Java API 中表示增补字符,包括单个字符和所有形式的字符序列。专家组考虑并排除了多种方法:
在这些方法中,一些在早期就被排除了。例如,重新定义基本类型 最终,专家组确定了一种分层的方法:
在需要时,此方法既能够提供一种概念简明且高效的单个字符表示法,又能够充分利用通过改进可支持增补字符的现有 API。同时,还能够促进字符序列在单个字符上的应用,这一点一般对于国际化的软件很有好处。 在这种方法中,一个 我们将在下面两部分中了解到 J2SE 平台的实质变化 — 其中一部分介绍单个代码点的低层 API,另一部分介绍采用字符序列的高层接口。 开放的增补字符:基于代码点的 API新增的低层 API 分为两大类:用于各种 最基本的转换方法是 几种用于分析代码单元和代码点的方法有助于转换过程:Character 类中的 但是,大多数基于代码点的方法均能够对所有 Unicode 字符实现基于
注意大多数接受代码点的方法并不检查给定的 API 包含许多简便的方法,这些方法可使用其他低层的 API 实现,但是专家组觉得,这些方法很常用,将它们添加到 J2SE 平台上很有意义。不过,专家组也排除了一些建议的简便方法,这给我们提供了一次展示自己实现此类方法能力的机会。例如,专家组经过讨论,排除了一种针对 /** * 创建仅含有指定代码点的新 String。 */ String newString(int codePoint) { return new String(Character.toChars(codePoint)); } 您会注意到,在这个简单的实现中, /** * 创建仅含有指定代码点的新 String。 * 针对 BMP 字符优化的版本。 */ String newString(int codePoint) { if (Character.charCount(codePoint) == 1) { return String.valueOf((char) codePoint); } else { return new String(Character.toChars(codePoint)); } } 或者,如果您需要创建许多个这样的 string,则可能希望编写一个重复使用 /** * 创建每一个均含有一个指定 * 代码点的新 String。 * 针对 BMP 字符优化的版本。 */ String[] newStrings(int[] codePoints) { String[] result = new String[codePoints.length]; char[] codeUnits = new char[2]; for (int i = 0; i < codePoints.length; i++) { int count = Character.toChars(codePoints[i], codeUnits, 0); result[i] = new String(codeUnits, 0, count); } return result; } 不过,最终您可能会发现,您需要的是一个完全不同的解决方案。新的构建器 System.out.println("Character " + String.valueOf(char) + " is invalid."); 新的格式化 API 支持增补文字,提供一种更加简单的备选方案: System.out.printf("Character %c is invalid.%n", codePoint); 使用此高层 API 不仅简捷,而它有很多特殊的优点:它可以避免串联(串联会使消息很难本地化),并将需要移进资源包 (resource bundle) 的字符串数量从两个减少到一个。 增补字符透视:功能增强在支持使用增补字符的 Java 2 平台中的大部分更改没有反映到新的 API 内。一般预期是,处理字符序列的所有接口将以适合其功能的方式处理增补字符。本部分着重讲述为达到此预期所作一些功能增强。 Java 编程语言中的标识符Java 语言规范指出所有 Unicode 字母和数字均可用于标识符。许多增补字符是字母或数字,因此 Java 语言规范已经参照新的基于代码点的方法进行更新,以在标识符内定义合法字符。为使用这些新方法,需要检测标识符的 javac 编译器和其他工具都进行了修订。 库内的增补字符支持许多 J2SE 库已经过增强,可以通过现有接口支持增补字符。以下是一些例子:
字符转换只有很少的字符编码可以表示增补字符。如果是基于 Unicode 的编码(如 UTF-8 和 UTF-16LE),则旧版的 J2RE 内的字符转换器已经按照正确处理增补字符的方式实现转换。对于 J2RE 5.0,可以表示增补字符的其他编码的转换器已更新:GB18030、x-EUC-TW(现在实现所有 CNS 11643 层面)和 Big5-HKSCS(现在实现 HKSCS-2001)。 在源文件内表示增补字符在 Java 编程语言源文件中,如果使用可以直接表示增补字符的字符编码,则使用增补字符最为方便。UTF-8 是最佳的选择。在所使用的字符编码无法直接表示字符的情况下,Java 编程语言提供一种 Unicode 转义符语法。此语法没有经过增强,无法直接表示增补字符。而是使用两个连续的 Unicode 转义符将其表示为 UTF-16 字符表示法中的两个编码单元。例如,字符 U+20000 写作“\uD840\uDC00”。您也许不愿意探究这些转义序列的含义;最好是写入支持所需增补字符的编码,然后使用一种工具(如 native2ascii)将其转换为转义序列。 遗憾的是,由于其编码问题,属性文件仍局限于 ISO 8859-1(除非您的应用程序使用新的 XML 格式)。这意味着您始终必须对增补字符使用转义序列,而且可能要使用不同的编码进行编写,然后使用诸如 native2ascii 的工具进行转换。 经修订的 UTF-8Java 平台对经修订的 UTF-8 已经很熟悉,但是,问题是应用程序开发人员在可能包含增补字符的文本和 UTF-8 之间进行转换时需要更加留神。需要特别注意的是,某些 J2SE 接口使用的编码与 UTF-8 相似但与其并不兼容。以前,此编码有时被称为“Java modified UTF-8”(经 Java 修订的 UTF-8) 或(错误地)直接称为“UTF-8”。对于 J2SE 5.0,其说明文档正在更新,此编码将统称为“modified UTF-8”(经修订的 UTF-8)。 经修订的 UTF-8 和标准 UTF-8 之间之所以不兼容,其原因有两点。其一,经修订的 UTF-8 将字符 U+0000 表示为双字节序列 0xC0 0x80,而标准 UTF-8 使用单字节值 0x0。其二,经修订的 UTF-8 通过对其 UTF-16 表示法的两个代理代码单元单独进行编码表示增补字符 。每个代理代码单元由三个字节来表示,共有六个字节。而标准 UTF-8 使用单个四字节序列表示整个字符。 Java 虚拟机及其附带的接口(如 Java 本机接口、多种工具接口或 Java 类文件)在 由于经修订的 UTF-8 与标准的 UTF-8 不兼容,因此切勿同时使用这两种版本的编码。经修订的 UTF-8 只能与上述的 Java 接口配合使用。在任何其他情况下,尤其对于可能来自非基于 Java 平台的软件的或可能通过其编译的数据流,必须使用标准的 UTF-8。需要使用标准的 UTF-8 时,则不能使用 Java 本机接口例程与经修订的 UTF-8 进行转换。 在应用程序内支持增补字符现在,对大多数读者来说最为重要的问题是:必须对应用程序进行哪些更改才能支持增补字符? 答案取决于在应用程序中进行哪种类型的文本处理和使用哪些 Java 平台 API。 对于仅以各种形式 对于本身解释单个字符、将单个字符传送给 Java 平台 API 或调用能够返回单个字符的方法的应用程序,则需要考虑这些字符的有效值。在很多情况下,往往不要求支持增补字符。例如,如果某应用程序搜索 只有在某应用程序本身解释单个字符、将单个字符传送给 Java 平台 API 或调用能够返回单个字符的方法且这些字符可能为增补字符时,才必须更改该应用程序。在提供使用 您可能会犹豫,是将所有文本转换为代码点表示法(即 对于需要与 UTF-8 之间进行转换的应用程序,还需要认真考虑是需要标准的 UTF-8 还是经修订的 UTF-8,并针对每种 UTF-8 采用适当的 Java 平台。“经修订的 UTF-8 ”部分介绍进行正确选择所需的信息。 使用增补字符测试应用程序经过前面部分的介绍后,无论您是否需要修订应用程序,测试应用程序是否运行正常始终是一种正确的做法。对于不含有图形用户界面的应用程序,有关“在源文件内表示增补字符 ” 的信息有助于设计测试用例。以下是有关使用图形用户界面进行测试的补充信息。 对于文本输入,Java 2 SDK 提供用于接受“\Uxxxxxx”格式字符串的代码点输入方法,这里大写的“U”表示转义序列包含六个十六进制数字,因此允许使用增补字符。小写的“u”表示转义序列“\uxxxx”的原始格式。您可以在 J2SDK 目录 demo/jfc/CodePointIM 内找到此输入方法及其说明文档。 对于字体渲染,您需要至少能够渲染一些增补字符的字体。其中一种此类字体为 James Kass 的 Code2001 字体,它提供手写体字形(如 Deseret 和 Old Italic)。利用 Java 2D 库中提供新功能,您只需将该字体安装到 J2RE 的 lib/fonts/fallback 目录内即可,然后它可自动添加至在 2D 和 XAWT 渲染时使用的所有逻辑字体 — 无需编辑字体配置文件。 至此,您就可以确认,您的应用程序能够完全支持增补字符了! 结论对增补字符的支持已经引入 Java 平台,大部分应用程序无需更改代码即可处理这些字符。解释单个字符的应用程序可以在 鸣谢Java 平台中的增补字符支持由 Java Community Process 的 JSR-204 专家组设计。技术规范设计主持为 Masayoshi Okutsu 和 Brian Beck (Sun Microsystems),其他专家组成员有 Craig Cummings (Oracle)、Mark Davis (IBM)、Markus Eble (SAP AG)、Jere Käpyaho (Nokia Corp.)、Kazuhiro Kazama (NTT)、Kenji Kazumura (Fujitsu Limited)、Eiichi Kimura (NEC Corp.)、Changshin Lee (Tmax Soft Inc.) 和 Toshiki Murata (Oki Electric Industry Co.)。参考实现由 Sun Microsystems 的 Java Internationalization 团队完成,并承蒙位于圣何塞的 IBM Globalization Center of Competency 的协助。技术规范的技术兼容套件为 Java Compatibility Kit,由 Sun Microsystems 的 JCK 团队实现。 参考书目Masayoshi Okutsu, Brian Beck (ed.): Unicode Supplementary Character Support. Proposed Final Draft . Sun Microsystems, 2004. Java 2 Platform Standard Edition 5.0 API Specification . Sun Microsystems, 2004. The Unicode Consortium: The Unicode Standard, Version 4.0 . Addison-Wesley, 2003. Ken Whistler, Mark Davis: Character Encoding Model . Unicode Technical Report #17. The Unicode Consortium, 2000. James Kass: Code2001, a Plane 1 Unicode-based Font . 关于作者Norbert Lindenberg 是 Sun Microsystems 的 Java Web Services 团队内 Java Internationalization 技术主管。在加盟 Sun 之前,曾经供职于 General Magic 和 Apple Computer,参与过多个国际化项目。他毕业于德国的卡尔斯鲁厄大学,拥有计算机科学理科硕士学位。 Masayoshi Okutsu 是 Sun Microsystems 的 Java Web Services 团队的一名国际化工程师,目前担任 Unicode Supplementary Character Support 的 Java Specification Request 204 的技术规范主管。在加盟 Sun Microsystems 之前,供职于 Digital Equipment Corporation,期间曾经参与多个国际化项目。他毕业于日本山形大学,拥有电子工程理学士学位。
1 本网站中使用的术语“Java 虚拟机”或“JVM”是指针对 Java 平台的虚拟机。 |