摘要
本文介 Java 平台支持增字符的方式。增字符是 Unicode 准中代点超出 U+FFFF 的字符,因此它无法在 Java 言中描述为单个的 16 体(例如char数据型)。些字符一般极少用,但是,有些会在如中文或日文人名中用到,因此,在东亚国家,政府用程序通常会要求支持些字符。
Java 平台目前正在改,以便支持字符的理,这种进对现有的用程序影响微乎其微。新的低 API 在需要使用个的字符运行。不,大多数文本 API 均使用字符序列,例如String或字符数在,些均解释为 UTF-16 序列,而且, API 实现转变为正确地理增字符。些改已融入 Java 2 平台 5.0 版,准版 (J2SE)
详细释这些改之外,本文同时为应用程序开发确定和实现必要的更改提供指,以支持整个 Unicode 字符集的使用。
背景
Unicode 最初设计是作固定度的 16 位字符编码。在 Java 言中,基本数据char初衷是通提供一种简单的、能包含任何字符的数据型来充分利用这种设计点。不在看来,16 编码的所有 65,536 个字符并不能完全表示全世界所有正在使用或曾使用的字符。于是,Unicode 准已展到包含多达 1,112,064 个字符。那些超出原来的 16 位限制的字符被称作增字符。Unicode 2.0 版是第一个包含启用增字符设计的版本,但是,直到 3.1 版才收入第一批增字符集。由于 J2SE 5.0 版必支持 Unicode 4.0 版,因此它必支持增字符。
字符的支持也可能会成为东亚的一个普遍商要求。政府用程序会需要些增字符,以正确表示一些包含罕中文字符的姓名。出版用程序可能会需要些增字符,以表示所有的古代字符和体字符。中国政府要求支持 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和字符“€”编码为20AC16Unicode 准始使用十六制数字,而且在在前面加上前“U+”,所以“A”编码书“U+0041”
点是指可用于编码字符集的数字。编码字符集定一个有效的代点范,但是并不一定将字符分配所有些代点。有效的 Unicode 点范 U+0000 U+10FFFFUnicode 4.0 将字符分配一百多万个代点中的 96,382 点。
字符是代点在 U+10000 U+10FFFF 的字符,也就是那些使用原始的 Unicode 16 设计无法表示的字符。从 U+0000 U+FFFF 的字符集有候被称基本多言面 (BMP)。因此,一个 Unicode 字符要属于 BMP,要属于增字符。
字符编码方案是从一个或多个编码字符集到一个或多个固定度代码单元序列的映射。最常用的代码单元是字,但是 16 位或 32 位整数也可用于内部理。UTF-32UTF-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+007FBasic Latin 字符子集,它对应 ASCII 字符集)。些字节值不会表示其他代点,一特性使 UTF-8 可以很方便地在件中将特殊的含义赋予某些 ASCII 字符。
下表所示几个字符不同表达方式的比
Unicode
U+0041
U+00DF
U+6771
U+10400
表示字形
UTF-32 码单
00000041
000000DF
00006771
00010400
UTF-16 码单
0041
00DF
6771
D801
DC00
UTF-8 码单
41
C3
9F
E6
9D
B1
F0
90
90
80

另外,本文在多地方使用术语字符序列或char序列概括 Java 2 平台识别的所有字符序列的容器:char[], java.lang.CharSequence实现(例如String),和java.text.CharacterIterator实现
这么术语。它与在 Java 平台中支持增字符有什么关系呢?
Java 平台中增字符的设计方法
JSR-204 作出的主要决定是如何在 Java API 中表示增字符,包括个字符和所有形式的字符序列。并排除了多方法:
字符编码 代码点                   重新定基本char,使其具有 32 位,这样也会使所有形式的char序列成 UTF-32 序列。
字符编码 代码点                   有的 16 char的基上,字符引入一新的 32 位基本型(例如,char32)。所有形式的 Char 序列均基于 UTF-16
字符编码 代码点                   有的 16 char的基上,字符引入一新的 32 位基本型(例如,char32)。StringStringBuffer接受并行 API,并将它释为 UTF-16 序列或 UTF-32 序列;其他char序列继续基于 UTF-16
字符编码 代码点                   使用int表示增的代点。StringStringBuffer接受并行 API,并将它释为 UTF-16 序列或 UTF-32 序列;其他char序列继续基于 UTF-16
字符编码 代码点                   使用代理char,表示增点。所有形式的char序列基于 UTF-16
字符编码 代码点                   引入一封装字符的StringStringBuffer接受新的 API,并将它释为字符的序列。
字符编码 代码点                   使用一个CharSequence例和一个索引的合表示代点。
些方法中,一些在早期就被排除了。例如,重新定基本char,使其具有 32 位,这对于全新的平台可能会非常有吸引力,但是, J2SE ,它会与有的 Java 1、序列化和其他接口不兼容,更不用基于 UTF-32 的字符串要使用两倍于基于 UTF-16 的字符串的内存了。添加一型的char32可能会简单一些,但是仍然会出机和序列化方面的问题。而且,言更改通常需要比 API 更改有更的提前期,因此,前面两方法会字符支持来无法接受的延了在余下的方法中筛选出最方案,实现使用四不同的方法,在大量行低字符理的代java.util.regex包)中实现字符支持,并对这方法的易程度和运行表现进行了比
确定了一的方法:
字符编码 代码点                   使用基本int在低 API 中表示代点,例如Character的静方法。
字符编码 代码点                   将所有形式的char序列均解释为 UTF-16 序列,并促其在更高层级 API 中的使用。
字符编码 代码点                   提供 API,以方便在各char和基于代点的表示法之转换
在需要,此方法既能提供一概念明且高效的个字符表示法,又能充分利用通可支持增字符的 API。同字符序列在个字符上的用,一点一般于国化的件很有好
这种方法中,一个char表示一个 UTF-16 码单元,这样对于表示代点有并不用。您会注意到,J2SE 术规在使用术语点和 UTF-16 码单元(表示法是相的)以及通用术语字符(表示法与该讨论没有系)。API 通常使用名称codePoint描述表示代点的int量,而 UTF-16 码单元的型当然char
将在下面两部分中了解到 J2SE 平台的实质变其中一部分介绍单个代点的低 API,另一部分介采用字符序列的高接口。
放的增字符:基于代点的 API
新增的低 API 两大:用于各char和基于代点的表示法之间转换的方法和用于分析和映射代点的方法。
最基本的转换方法是Character.toCodePoint(char high, char low)(用于将两个 UTF-16 码单转换为一个代点)和Character.toChars(int codePoint)(用于将指定的代转换为一个或两个 UTF-16 码单元,然后封装到一个char[]内。不,由于大多数情况下文本以字符序列的形式出,因此,另外提供codePointAtcodePointBefore方法,用于将代点从各字符序列表示法中提取出来:Character.codePointAt(char[] a, int index)String.codePointBefore(int index)是两典型的例子。在将代点插入字符序列,大多数情况下均有一些针对StringBufferStringBuilderappendCodePoint(int codePoint)方法,以及一个用于提取表示代点的int[]String构建器。
用于分析代码单元和代点的方法有助于转换过程:Character 中的isHighSurrogateisLowSurrogate方法可以识别用于表示增字符的charcharCount(int codePoint)方法可以确定是否需要将某个代转换为一个或两个char
但是,大多数基于代点的方法均能够对所有 Unicode 字符实现基于char的旧方法 BMP 字符所实现的功能。以下是一些典型例子:
字符编码 代码点                   Character.isLetter(int codePoint)可根据 Unicode 识别字母。
字符编码 代码点                   Character.isJavaIdentifierStart(int codePoint)可根据 Java 范确定代点是否可以启动标识
字符编码 代码点                   Character.UnicodeBlock.of(int codePoint)可搜索代点所属的 Unicode 字符子集。
字符编码 代码点                   Character.toUpperCase(int codePoint)可将定的代转换为其大写等字符。尽管此方法能支持增字符,但是它仍然不能解决根本的问题,即在某些情况下,逐个字符的转换无法正确完成。例如,德文字符“"ß"”应该转换为“SS”需要使用String.toUpperCase方法。
注意大多数接受代点的方法并不检查给定的int是否于有效的 Unicode 点范之内(如上所述,只有 0x0 0x10FFFF 的范是有效的)。在大多数情况下,该值是以确保其有效的方法生的,在些低 API 中反复检查其有效性可能会性能造成面的影响。在无法确保有效性的情况下,用程序必使用Character.isValidCodePoint方法确保代点有效。大多数方法于无效的代点采取的行没有特加以指定,不同的实现可能会有所不同。
API 包含便的方法,些方法可使用其他低 API 实现,但是组觉些方法很常用,将它添加到 J2SE 平台上很有意。不也排除了一些建便方法,这给提供了一次展示自己实现方法能力的机会。例如,组经过讨论,排除了一种针对String的新构建器(构建器可以建一个保持个代点的String)。以下是使用程序使用有的 API 提供功能的一种简便方法:
/**
 * 含有指定代点的新 String。
 */
String newString(int codePoint) {
    return new String(Character.toChars(codePoint));
}
您会注意到,在简单实现中,toChars方法始终创建一个中数列,数列使用一次即立即弃。如果方法在您的性能估中出,您可能会希望将其为针对普通的情况,即 BMP 字符:
/**
 * 含有指定代点的新 String
 * 针对 BMP 字符化的版本。
 */
String newString(int codePoint) {
    if (Character.charCount(codePoint) == 1) {
        return String.valueOf((char) codePoint);
    } else {
        return new String(Character.toChars(codePoint));
    }
}
或者,如果您需要多个这样 string可能希望写一个重使用toChars方法所使用的数列的通用版本:
/**
 * 一个均含有一个指定
 * 点的新 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;
}
,最您可能会发现,您需要的          是一个完全不同的解决方案。新的构建器String(int codePoint)实际上建String.valueOf(char)的一个基于代点的备选方案。在很多情况下,此方法用于消息生成的境,例如:
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 准中定的特殊大小写规则
字符编码 代码点                   java.util.regex包已更新,这样模式字符串和目字符串均可以包含增字符并将其作完整理。
字符编码 代码点                   在,在java.text包内行整理,会将增字符看作完整元。
字符编码 代码点                   java.text.Bidi已更新,可以理增字符和 Unicode 4.0 中新增的其他字符。注意,Cypriot Syllabary 字符子集内的增字符具有从右至左的方向性。
字符编码 代码点                   Java 2D API 内的字体渲染和打印技经过,可以正确渲染和量包含增字符的字符串。
字符编码 代码点                   Swing 文本实现已更新,可以理包含增字符的文本。
字符转换
只有很少的字符编码可以表示增字符。如果是基于 Unicode 编码(如 UTF-8 UTF-16LE),旧版的 J2RE 内的字符转换器已按照正确理增字符的方式实现转换 J2RE 5.0,可以表示增字符的其他编码转换器已更新:GB18030x-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-8
Java 平台对经 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 文件)在java.io.DataInputDataOutput接口和中使用 UTF-8 实现或使用些接口和,并行序列化。Java 本机接口提供与 UTF-8 间进转换的例程。而 UTF-8 Stringjava.io.InputStreamReaderOutputStreamWriterjava.nio.charset (facility) 以及多其上 API 提供支持。
由于 UTF-8 准的 UTF-8 不兼容,因此切勿同使用版本的编码 UTF-8 只能与上述的 Java 接口配合使用。在任何其他情况下,尤其于可能来自非基于 Java 平台的件的或可能通编译的数据流,必使用准的 UTF-8。需要使用准的 UTF-8 不能使用 Java 本机接口例程与 UTF-8 转换
用程序内支持增字符
在,大多数者来重要的问题是:必须对应用程序行哪些更改才能支持增字符?
答案取决于在用程序中行哪种类型的文本理和使用哪些 Java 平台 API
以各形式char序列([char[]java.lang.CharSequence实现java.text.CharacterIterator实现理文本和使用接受和退回序列(如char序列)的 Java API 用程序,可能根本不需要行任何更改。Java 平台 API 实现应该够处理增字符。
于本身解释单个字符、将个字符 Java 平台 API 用能返回个字符的方法的用程序需要考虑这些字符的有效。在很多情况下,往往不要求支持增字符。例如,如果某用程序搜索char序列中的 HTML 标记,并逐一检查每char,它会知道标记仅使用 Basic Latin 字符子集中的字符。如果所搜索的文本含有增字符,则这些字符不会与标记字符混淆,因 UTF-16 使用代码单元表示增字符,而代码单元的不会用于 BMP 字符。
只有在某用程序本身解释单个字符、将个字符 Java 平台 API 用能返回个字符的方法且些字符可能字符,才必更改该应用程序。在提供使用char序列的并行 API ,最好而使用此 API。在其他情况下,有必要使用新的 API char和基于代点的表示法之间进转换,并用基于代点的 API。当然,如果您发现 J2SE 5.0 中有更新、更方便的 API,使您能支持增字符并同时简化代(如上格式化范例中所述),没有必要这样做。
您可能会犹豫,是将所有文本转换为点表示法(即int[])然后在表示法中理,是在大多数情况下仍采用char序列,在需要时转换为点,两者之孰劣很确定。当然,体来Java 平台 API char序列肯定具有一定的优势,而且采用 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 平台,大部分用程序无需更改代即可些字符。解释单个字符的用程序可以在Character和多CharSequence中使用基于代点的新 API
 
 
 
以下是UnicodeUTF-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
 
Byte 数组转整数:
static int bytes2int(byte[] b)
{
         int mask=0xff;
         int temp=0;
      
  int res=0;
        for(int i=0;i<4;i++){
            res<<=8;
            temp=b[i]&mask;
            res|=temp;
        }
       return res;
}
整数转byte数组:
static byte[] int2bytes(int num)
{
       byte[] b=new byte[4];
       int mask=0xff;
       for(int i=0;i<4;i++){
            b[i]=(byte)(num>>>(24-i*8));
       }
      return b;
}
 
 

相关文章: