摘要
本文介
绍
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
和字符
“€”
的
编码为
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
字符。
下表所示
为
几个字符不同表达方式的比
较
:
Unicode
代
码
点
|
U+0041
|
U+00DF
|
U+6771
|
U+10400
| ||||||||||
表示字形
| ||||||||||||||
UTF-32
代
码单
元
|
|
|
|
| ||||||||||
UTF-16
代
码单
元
|
|
|
|
| ||||||||||
UTF-8
代
码单
元
|
|
|
|
|
另外,本文在 许 多地方使用 术语 字符序列或 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
)。
String
和
StringBuffer
接受并行
API
,并将它
们
解
释为
UTF-16
序列或
UTF-32
序列;其他
char
序列
继续
基于
UTF-16
。
使用
int
表示增
补
的代
码
点。
String
和
StringBuffer
接受并行
API
,并将它
们
解
释为
UTF-16
序列或
UTF-32
序列;其他
char
序列
继续
基于
UTF-16
。
使用代理
char
对
,表示增
补
代
码
点。所有形式的
char
序列基于
UTF-16
。
引入一
种
封装字符的
类
。
String
和
StringBuffer
接受新的
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[]
内。不
过
,由于大多数情况下文本以字符序列的形式出
现
,因此,另外提供
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.");
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;
}
{
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;
}
{
byte[] b=new byte[4];
int mask=0xff;
for(int i=0;i<4;i++){
b[i]=(byte)(num>>>(24-i*8));
}
return b;
}