开放的增补字符:基于代码点的
API
新增的低层
API
分为两大类:用于各种
char
和基于代码点的表示法之间转换的方法和用于分析和映射代码点的方法。
最基本的转换方法是
Character.toCodePoint
(
char high
,
char low
)(用于将两个
UTF-16
代码单元转换为一个代码点)和
Character.toChars
(
int codePoint
)(用于将指定的代码点转换为一个或两个
UTF-16
代码单元,然后封装到一个
char[]
内。不过,由于大多数情况下文本以字符序列的形式出现,因此,另外提供
codePointAt
和
codePointBefore
方法,用于将代码点从各种字符序列表示法中提取出来:
Character.codePointAt
(
char[] a
,
int index
)和
String.codePointBefore
(
int index
)是两种典型的例子。在将代码点插入字符序列时,大多数情况下均有一些针对
StringBuffer
和
StringBuilder
类的
appendCodePoint
(
int codePoint
)方法,以及一个用于提取表示代码点的
int[]
的
String
构建器。
几种用于分析代码单元和代码点的方法有助于转换过程:
Character
类中的
isHighSurrogate
和
isLowSurrogate
方法可以识别用于表示增补字符的
char
值;
charCount
(
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
,可以表示增补字符的其他编码的转换器已更新:
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-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.DataInput
和
DataOutput
接口和类中使用经修订的
UTF-8
实现或使用这些接口和类,并进行序列化。
Java
本机接口提供与经修订的
UTF-8
之间进行转换的例程。而标准
UTF-8
由
String
类、
java.io.InputStreamReader
和
OutputStreamWriter
类、
java.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.
以下是
Unicode
和
UTF-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
;
}