Java汉字排序(2)按拼音排序
1.前言
对于包含汉字的字符串来说,排序的方式主要有两种:
一种是拼音,一种是笔画。
本文就讲述如何实现按拼音排序的比较器(Comparator)。
作者:Jeff 发表于:2007年12月21日 11:27 最后更新于: 2007年12月21日 12:38
版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明。
http://www.blogjava.net/jeff-lau/archive/2007/12/21/169257.html
2.拼音排序
拼音有好几种方式,其中最主要的是中华人民共和国的汉语拼音 Chinese Phonetic。对汉字的排序有两种:一种是宽松的,能够按拼音排序最常用的汉字,另一种是严格的,能够按拼音排序绝大部分大部分汉字。
2.1宽松的拼音排序法
原理:汉字最早是GB2312编码,收录了六千多个汉字,是按拼音排序的,编码是连续的。 后来出现了GBK编码,对GB2312进行了扩展,到了两万多汉字,并且兼容GB2312,也就是说GB2312中的汉字编码是原封不动搬到GBK中的(在GBK编码中[B0-D7]区中)。
如果只关心这6000多个汉字的顺序,就可以用下面的方法实现汉字宽松排序。
1 /** 2 * @author Jeff 3 * 4 * Copyright (c) 复制或转载本文,请保留该注释。 5 */ 6 7 package chinese.utility; 8 9 import java.text.Collator; 10 import java.util.Comparator; 11 import java.util.Locale; 12 13 public class PinyinSimpleComparator implements Comparator<String> { 14 public int compare(String o1, String o2) { 15 return Collator.getInstance(Locale.CHINESE).compare(o1, o2); 16 } 17 }
在对[孙, 孟, 宋, 尹, 廖, 张, 徐, 昆, 曹, 曾,怡]这几个汉字排序,结果是:[曹, 昆, 廖, 孟, 宋, 孙, 徐, 尹, 曾, 张, 怡]。最后一个 怡 有问题,不该排在最后的。
注意:这个程序有两个不足
- 由于gb2312中的汉字编码是连续的,因此新增加的汉字不可能再按照拼音顺序插入到已有的gb2312编码中,所以新增加的汉字不是按拼音顺序排的。
- 同音字比较的结果不等于0 。
下面的测试代码可以证明
1 /** 2 * @author Jeff 3 * 4 * Copyright (c) 复制或转载本文,请保留该注释。 5 */ 6 7 /** 8 * 非常用字(怡) 9 */ 10 @Test 11 public void testNoneCommon() { 12 Assert.assertTrue(comparator.compare("怡", "张") > 0); 13 } 14 15 /** 16 * 同音字 17 */ 18 @Test 19 public void testSameSound() { 20 Assert.assertTrue(comparator.compare("怕", "帕") != 0); 21 }
2.2严格的拼音排序法
为了解决宽松的拼音的两点不足,可以通过实现汉语拼音的函数来解决。goolge下看到sf上有个pinyin4j的项目,可以解决这个问题,pinyin4j的项目地址是:http://pinyin4j.sourceforge.net/。
实现代码:
1 /** 2 * @author Jeff 3 * 4 * Copyright (c) 复制或转载本文,请保留该注释。 5 */ 6 package chinese.utility; 7 8 import java.util.Comparator; 9 import net.sourceforge.pinyin4j.PinyinHelper; 10 11 public class PinyinComparator implements Comparator<String> { 12 13 public int compare(String o1, String o2) { 14 15 for (int i = 0; i < o1.length() && i < o2.length(); i++) { 16 17 int codePoint1 = o1.charAt(i); 18 int codePoint2 = o2.charAt(i); 19 20 if (Character.isSupplementaryCodePoint(codePoint1) 21 || Character.isSupplementaryCodePoint(codePoint2)) { 22 i++; 23 } 24 25 if (codePoint1 != codePoint2) { 26 if (Character.isSupplementaryCodePoint(codePoint1) 27 || Character.isSupplementaryCodePoint(codePoint2)) { 28 return codePoint1 - codePoint2; 29 } 30 31 String pinyin1 = pinyin((char) codePoint1); 32 String pinyin2 = pinyin((char) codePoint2); 33 34 if (pinyin1 != null && pinyin2 != null) { // 两个字符都是汉字 35 if (!pinyin1.equals(pinyin2)) { 36 return pinyin1.compareTo(pinyin2); 37 } 38 } else { 39 return codePoint1 - codePoint2; 40 } 41 } 42 } 43 return o1.length() - o2.length(); 44 } 45 46 /** 47 * 字符的拼音,多音字就得到第一个拼音。不是汉字,就return null。 48 */ 49 private String pinyin(char c) { 50 String[] pinyins = PinyinHelper.toHanyuPinyinStringArray(c); 51 if (pinyins == null) { 52 return null; 53 } 54 return pinyins[0]; 55 } 56 }
3.测试
1 /** 2 * @author Jeff 3 * 4 * Copyright (c) 复制或转载本文,请保留该注释。 5 */ 6 package chinese.utility.test; 7 8 import java.util.Comparator; 9 10 import org.junit.Assert; 11 import org.junit.Test; 12 13 import chinese.utility.PinyinComparator; 14 15 public class PinyinComparatorTest { 16 17 private Comparator<String> comparator = new PinyinComparator(); 18 19 /** 20 * 常用字 21 */ 22 @Test 23 public void testCommon() { 24 Assert.assertTrue(comparator.compare("孟", "宋") < 0); 25 } 26 27 /** 28 * 不同长度 29 */ 30 @Test 31 public void testDifferentLength() { 32 Assert.assertTrue(comparator.compare("他奶奶的", "他奶奶的熊") < 0); 33 } 34 35 /** 36 * 和非汉字比较 37 */ 38 @Test 39 public void testNoneChinese() { 40 Assert.assertTrue(comparator.compare("a", "阿") < 0); 41 Assert.assertTrue(comparator.compare("1", "阿") < 0); 42 } 43 44 /** 45 * 非常用字(怡) 46 */ 47 @Test 48 public void testNoneCommon() { 49 Assert.assertTrue(comparator.compare("怡", "张") < 0); 50 } 51 52 /** 53 * 同音字 54 */ 55 @Test 56 public void testSameSound() { 57 Assert.assertTrue(comparator.compare("怕", "帕") == 0); 58 } 59 60 /** 61 * 多音字(曾) 62 */ 63 @Test 64 public void testMultiSound() { 65 Assert.assertTrue(comparator.compare("曾经", "曾迪") > 0); 66 } 67 68 }
这样严格的拼音排序还是有有待改进的地方,看上面测试代码的最后一个测试,就会发现:程序不会根据语境来判断多音字的拼音,仅仅是简单的取多音字的第一个拼音。