1
问题提出
汉字排序不同于英文字母的排序,主要有两种排序方式:一种是按拼音首字母排序;一种是按笔画排序。大多数情况下是按拼音首字母排序。那汉字究竟怎样通过其拼音首字母排序呢?好在字符集帮我们解决了这个问题。
众所周知的包含汉字的字符集有gb2312和GBK,后者是前者的扩展。Gb2312在设计的时候就将常用的中国汉字按照拼音的顺序包含到字符集中,因此,我们通过汉字的字符编码就可以判断汉字的拼音顺序。因为GBK是gb2312的扩展,对gb2312完全兼容,只是在gb2312的字符集末尾加入了二次常用汉字,所以GBK字符集也可以通过这种方法实现拼音排序。
2
问题解决
Java运行时的编码是Unicode编码,所有的字符集都要转化成Unicode编码,所以,可以很方便的对gb2312和GBK字符集的汉字实现拼音排序。
测试代码如下:
public
class NormalComparator
implements Comparator<Object> {
RuleBasedCollator collator = (RuleBasedCollator)Collator.getInstance(Locale.CHINA);
public
int compare(Object o1, Object o2) {
//
TODO Auto-generated method stub
return
collator.compare(o1.toString(), o2.toString());
}
}
代码说明:
Collator 类是执行区分语言环境的 String
比较,可以使用静态工厂方法 getInstance
来为给定的语言环境获得适当的Collator
对象。如Collator.getInstance(Local.CHINA)是获得中国语言的Collator对象。
RuleBasedCollator
是Collator的子类,它实现了特定整理策略的细节或者需要修改策略。为了提高效率,对 RuleBasedCollator
做了如下限制(其他子类可以用于更复杂的语言):
l
?
如果指定了由 <modifier>
控制的特殊整理规则,则它将用于整个 collator
对象。
l
? 所有未指定的字符位于整理顺序的末尾。
整理表由一组整理规则组成,其中每个规则是以下三种形式之一:
<modifier>
<relation> <text-argument>
<reset> <text-argument>
规则元素的定义如下:
l
文本参数:文本参数可以是任何的字符序列,不包括特殊字符(即公共空白字符 [0009-000D、0020]
和规则语法字符 [0021-002F、003A-0040、005B-0060、007B-007E])。如果需要使用这些字符,可以把它们放在单引号内(例如
& => '&')。注意,没有使用引号的空白字符将被忽略;例如 b c
视为 bc。
l
修饰符:目前有两个修饰符用于开启特殊的整理规则。
? '@' :
开启重音字符的反向排序(二级区别),以法语为例。
? '!' :
开启 Thai/Lao 元音-辅音字母交换。如果当 \U0E40-\U0E44
范围内的 Thai 元音字母排在 \U0E01-\U0E2E
范围内的 Thai 辅音字母前面,或者 \U0EC0-\U0EC4
范围内的 Lao 元音字母排在 \U0E81-\U0EAE
范围内的 Lao 辅音字母前面时此规则有效,那么经过整理后元音字母将被放置在辅音字母的后面。
'@' : 指示重音字符按反向排序,以法语为例。
l
关系:关系如下:
? '<' :
大于,当字母不同时(一级)
? ';' :
大于,当重音不同时(二级)
? ',' :
大于,当大小写不同时(三级)
? '=' :
等于
l
重置:存在单一的重置主要用于规则集的缩减和扩充,但它也可以用于在规则集的末尾添加修改。
'&' : 指示下一条规则在重置文本参数将要被排序的位置后面。
通过RuleBasedCollator类,我们可以对已有的整理规则进行修改,如:
String rule = “<a<b<f<g”;
String addrule = “&b<e”;
String newrule = rule+addrule;
RuleBasedCollator new_collator = new RuleBasedCollator(newrule);
新的规则将是:a,b,e,f,g
RuleBasedCollator collator_us = Collator.getInstance(Local.US);
RuleBasedCollator collator_ch = Collator.getInstance(Local.CHINA);
RuleBasedCollator new_collator = new RuleBasedCollator(collator_us.getRules()+collator_ch.getRules());
New_collator是英文和中文整理规则的组合。
因此,我们通过以上方式可以很方便的定义和修改整合规则。这对我们下面的讨论有很大的帮助。
Arrays.sort(array,comparator)方法是对数组array按照comparator定义的规则进行排序。与排序有关的接口和类有Comparator、Comparable和Collator。testComparator类实现了Comparator接口的compare方法,这是Arrays.sort()和Comparator之间的契约。同时也是Collections.sort()与Comparator之间的契约。其中Arrays.sort()是对数组排序,而Collections是对Set和ArrayList进行排序。如果不用Comparator,也可以由集合元素对象本身实现Comparable的compareTo()方法作为Arrays.sort()或Collections.sort()的之间的契约。
3
问题扩展
如果对排序的结果要求不是很严格,上面的测试代码就足够了。但是如果我们在"板球", "排球", "香港", "足球", "篮球"的后面加上非常用字“怡”再次用上面的测试代码排序时,会发现它就不是那么灵验了。
这主要是因为gb2312中的汉字为常用汉字,并且是按拼音顺序排列的。但GBK在gb2312的基础上扩展了非常用字符,这些字符并不是按拼音顺序排列的,而是按笔画顺序排列。因此,如果有非常用字符时,排序结果就会有出入。
为了解决这个问题,我们有两种方案:
l
取排序的汉字的汉语拼音,再按照英文字符的比较方法进行排序。
l
扩展常用字符的比较规则,使其可以同时对常用和非常用字符进行排序。
第一种方案是我们最容易想到的,我们可以借助于Google的开源项目pinyin4j来帮助我们完成;第二种方案基于RuleBaseCollator的整合规则。
3.1
扩展比较规则
通过RuleBaseCollator的getRules方法可以获得已有的规则,那怎样把非常用字的规则整合到原有的规则中呢?现在大多数系统还只能支持Unicode中的基本汉字那部分汉字,编码从U9FA6-U9FBF。所以我们可以按照下面的方法:
首先,用java程序生成一个文本文件(full_b.csv)。包括所有的从U9FA6-U9FBF的字符的编码和文字。利用excel的按拼音排序功能,对full_b.csv文件中的内容排序。
然后,删除第一列数据,只留下汉字。
最后,用java程序读取full_b.csv文件,生成新的整合规则,与原有的规则进行组合生成新的规则比较器。
通过上面的方法我们就可以扩展比较规则,对绝大多数的汉字正确的排序。
代码试例:
//
建立文件
PrintWriter out = new PrintWriter("c:\\full_b.csv");
//
基本汉字
for (char
c = 0x4E00; c <= 0x9FA5; c++) {
out.println((int) c +
"," + c);
}
out.flush();
out.close();
排序,删除第一列数据,只留下汉字。
//
生成规则
Scanner in = new Scanner(new
File("c:\\full_b.csv"));
PrintWriter out = new PrintWriter("c:\\full.csv");
int stroke = 0;
while (in.hasNextLine()) {
String line = in.nextLine();
if (in.hasNextLine())
out.print(line + "<");
else
out.print(line);
}
out.flush();
out.close();
in.close();
建立规则
/**
*
构建新的比较规则
*
@author
User
*/
public
class Rules {
private
static String
fileName =
"Chinese.csv";
private
static RuleBasedCollator
collatorIns;
private
static StringBuffer load() {
InputStream in = Rules.class.getResourceAsStream(fileName);
StringBuffer sb = new StringBuffer();
Scanner sca = new Scanner(in);
while (sca.hasNextLine()) {
sb.append("<" + sca.nextLine());
}
return sb;
}
public
static RuleBasedCollator getCollator() {
if (collatorIns
== null) {
RuleBasedCollator collator = (RuleBasedCollator) Collator
.getInstance(Locale.CHINA);
try {
collatorIns =
new RuleBasedCollator(collator.getRules()
.substring(0, 2125)
+ load());
} catch (ParseException e) {
e.printStackTrace();
}
}
return
collatorIns;
}
}
比较器
public
class FullComparator
implements Comparator {
RuleBasedCollator collator = Rules.getCollator();
public
int compare(Object o1, Object o2) {
return
collator.compare(o1.toString(), o2.toString());
}
}
3.2
借助pinyin4j
Google的pinyin4j项目可以将汉字转化为汉语拼音,借助于它,我们可以将常用和非常用汉字按照拼音的顺序排序。对于多音字我们只取第一个音节。
测试代码:
public
class PinyinComparator
implements Comparator {
public
int compare(Object o1, Object o2) {
String key1 = o1.toString();
String key2 = o2.toString();
for (int
i = 0; i < key1.length() && i < key2.length(); i++) {
int codePoint1 = key1.charAt(i);
int codePoint2 = key2.charAt(i);
if (Character.isSupplementaryCodePoint(codePoint1)
|| Character.isSupplementaryCodePoint(codePoint2)) {
i++;
}
if (codePoint1 != codePoint2) {
if (Character.isSupplementaryCodePoint(codePoint1)
|| Character.isSupplementaryCodePoint(codePoint2)) {
return codePoint1 - codePoint2;
}
String pinyin1 = pinyin((char) codePoint1);
String pinyin2 = pinyin((char) codePoint2);
if (pinyin1 !=
null && pinyin2 !=
null) {
//
两个字符都是汉字
if (!pinyin1.equals(pinyin2)) {
return pinyin1.compareTo(pinyin2);
}
} else {
return codePoint1 - codePoint2;
}
}
}
return key1.length() - key2.length();
}
private String pinyin(char
c) {
String[] pinyins = PinyinHelper.toHanyuPinyinStringArray(c);
if (pinyins ==
null) {
return
null;
//