早期 Java 版本使用 16 位 char 数据类型表示 Unicode 字符。这种设计方 法有时比较合理,因为所有 Unicode 字符拥有的值都小于 65,535 (0xFFFF), 可以通过 16 位表示。但是,Unicode 后来将最大值增加到 1,114,111 (0x10FFFF)。由于 16 位太小,不能表示 Unicode version 3.1 中的所有 Unicode 字符,32 位值 — 称为码位(code point) — 被用于 UTF-32 编码模式。
但与 32 位值相比,16 位值的内存使用效率更高, 因此 Unicode 引入了一个种新设计方法来允许继续使用 16 位值。UTF-16 中采 用的这种设计方法分配 1,024 值给 16 位高代理(high surrogate),将另外 的 1,024 值分配给 16 位低代理(low surrogate)。它使用一个高代理加上一 个低代理 — 一个代理对(surrogate pair) — 来表示 65,536 (0x10000) 和 1,114,111 (0x10FFFF) 之间的 1,048,576 (0x100000) 值 (1,024 和 1,024 的乘积)。
Java 1.5 保留了 char 类型的行为来表 示 UTF-16 值(以便兼容现有程序),它实现了码位的概念来表示 UTF-32 值。这个扩展(根据 JSR 204:Unicode Supplementary Character Support 实现) 不需要记住 Unicode 码位或转换算法的准确值 — 但理解代理 API 的正 确用法很重要。
东亚国家和地区近年来增加了它们的字符集中的字符数 量,以满足用户需求。这些标准包括来自中国的国家标准组织的 GB 18030 和来 自日本的 JIS X 0213。因此,寻求遵守这些标准的程序更有必要支持 Unicode 代理对。本文解释相关 Java API 和编码选项,面向计划重新设计他们的软件, 从只能使用 char 类型的字符转换为能够处理代理对的新版本的读者。
顺序访问
顺序访问是在 Java 语言中处理字符串的一个基本操作。在 这种方法下,输入字符串中的每个字符从头至尾按顺序访问,或者有时从尾至头 访问。本小节讨论使用顺序访问方法从一个字符串创建一个 32 位码位数组的 7 个技术示例,并估计它们的处理时间。
示例 1-1:基准测试(不支持代 理对)
清单 1 将 16 位 char 类型值直接分配给 32 位码位值,完全没 有考虑代理对:
清单 1. 不支持代理对
int[] toCodePointArray(String str) { // Example 1-1
int len = str.length(); // the length of str
int[] acp = new int[len]; // an array of code points
for (int i = 0, j = 0; i < len; i++) {
acp[j++] = str.charAt(i);
}
return acp;
}
尽管这个示例不支持代理对,但它提供了一个处理时间基准来比 较后续顺序访问示例。
示例 1-2:使用 isSurrogatePair()
清单 2 使用 isSurrogatePair() 来计算代理对总数。计数之后,它分配足够的内存 以便一个码位数组存储这个值。然后,它进入一个顺序访问循环,使用 isHighSurrogate() 和 isLowSurrogate() 确定每个代理对字符是高代理还是低 代理。当它发现一个高代理后面带一个低代理时,它使用 toCodePoint() 将该 代理对转换为一个码位值并将当前索引值增加 2。否则,它将这个 char 类型值 直接分配给一个码位值并将当前索引值增加 1。这个示例的处理时间比 示例 1 -1 长 1.38 倍。
清单 2. 有限支持
int[] toCodePointArray(String str) { // Example 1-2
int len = str.length(); // the length of str
int[] acp; // an array of code points
int surrogatePairCount = 0; // the count of surrogate pairs
for (int i = 1; i < len; i++) {
if (Character.isSurrogatePair(str.charAt(i - 1), str.charAt(i))) {
surrogatePairCount++;
i++;
}
}
acp = new int[len - surrogatePairCount];
for (int i = 0, j = 0; i < len; i++) {
char ch0 = str.charAt(i); // the current char
if (Character.isHighSurrogate(ch0) && i + 1 < len) {
char ch1 = str.charAt(i + 1); // the next char
if (Character.isLowSurrogate(ch1)) {
acp[j++] = Character.toCodePoint(ch0, ch1);
i++;
continue;
}
}
acp[j++] = ch0;
}
return acp;
}