对于包含汉字的字符串来说,排序的方式主要有两种:一种是拼音,一种是笔画。
本文就讲述如何实现按笔划排序的比较器(Comparator)。
作者:Jeff 发表于:2007年12月21日 11:27 最后更新于: 2007年12月21日 12:38
版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明。
http://www.blogjava.net/jeff-lau/archive/2007/12/21/169257.html
笔画排序
要按笔画排序,就要实现笔画比较器。
class StokeComparator implements Comparator<String>
如果有个方法可以求得汉字的笔画数,上面的功能就很容易实现。如何求一个汉字的笔画数?最容易想到的就是查表法。建一个汉字笔画数表,如:
汉字 | Unicode编码 | 笔画数 |
一 | U4E00 | 1 |
二 | U4E8C | 2 |
龍 | U9F8D | 16 |
... | ... | ... |
表二
如果是连续的、按unicode编码排好顺序的表,实际存储在笔画数表中的只需最后一列就够了。
那如何建这个表呢?这个表存储在哪里?
建汉字笔画数表
现在大多数系统还只能支持Unicode中的基本汉字那部分汉字,编码从U9FA6-U9FBF。所以我们只建这部分汉字的笔画表。汉字笔画数表,我们可以按照下面的方法生成:
- 用java程序生成一个文本文件(Chinese.csv)。包括所有的从U9FA6-U9FBF的字符的编码和文字。利用excel的按笔画排序功能,对Chinese.csv文件中的内容排序。
- 编写Java程序分析Chinese.csv文件,求得笔画数, 生成ChineseStroke.csv。矫正笔画数,重新按汉字的Unicode编码对ChineseStroke.csv文件排序。
- 只保留ChineseStroke.csv文件的最后一列,生成Stroke.csv。
在这里 下载上面3个步骤生成的3个文件。
生成Chinese.csv的Java程序
/**
* @author Jeff
*
* Copyright (c) 复制或转载本文,请保留该注释。
*/
package chinese.utility.preface; import java.io.IOException;
import java.io.PrintWriter; public class ChineseCoder { public static void main(String[] args) throws IOException {
PrintWriter out = new PrintWriter("Chinese.csv");
// 基本汉字
for (char c = 0x4E00; c <= 0x9FA5; c++) {
out.println((int) c + "," + c);
}
out.flush();
out.close(); } }
初始化笔画数
从Excel排序过后的Chinese.csv文件来看,排好序的文件还是有一定规律的。在文件的第9行-12行可以看出:逐行扫描的时候,当unicode会变小了,笔画数也就加1。
20059,乛
20101,亅
19969,丁
19970,丂
用下面的Java程序分析吧。
/**
* @author Jeff
*
* Copyright (c) 复制或转载本文,请保留该注释。
*/
package chinese.utility.preface; import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Scanner; public class Stroke { /**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
Scanner in = new Scanner(new File("Chinese.csv"));
PrintWriter out = new PrintWriter("ChineseStroke.csv");
String oldLine = "";
int stroke = ;
while (in.hasNextLine()) {
String line = in.nextLine();
if (line.compareTo(oldLine) < ) {
stroke++;
}
oldLine = line;
out.println(line + "," + stroke);
}
out.flush();
out.close();
in.close();
} }
上面用的这个规律有问题吗?有问题,从ChineseStroke.csv文件抽取最后几个汉字就发现,笔画数不对。为什么呢?
- 笔画数可能不是连续的。
- n+1笔画数的最小Unicode码可能比n笔画数的最大Unicode码要大
我们要人工核对ChineseStroke文件,但只要核对在笔画变化的那几个汉字的笔画数。最后,我发现,只有笔画数多于30的少数几个汉字的笔画数不对。核对并矫正笔画数后,用Excel按Unicode重新排序,去掉汉字和Unicode两列,只保留笔画数那列,得到Stroke.csv文件。
求得笔画数的方法和笔画比较器方法
求得笔画数的方法测试代码:
/**
* @author Jeff
*
* Copyright (c) 复制或转载本文,请保留该注释。
*/
package chinese.utility.test; import static org.junit.Assert.assertEquals; import org.junit.Before;
import org.junit.Test;
import chinese.utility.Chinese; public class StrokeTest { Chinese chinese; @Before
public void setUp() {
chinese = new Chinese();
} @Test
public void testStroke() {
assertEquals(, chinese.stroke('一'));
} @Test
public void testStroke2() {
assertEquals(, chinese.stroke('二'));
} @Test
public void testStroke16() {
assertEquals(, chinese.stroke('龍'));
} @Test
public void testStrokeABC() {
assertEquals(-, chinese.stroke('a'));
} }
求得笔画数的方法代码
/**
* @author Jeff
*
* Copyright (c) 复制或转载本文,请保留该注释。
*/
package chinese.utility; import java.util.Comparator; public class StrokeComparator implements Comparator<String> { public int compare(String o1, String o2) { Chinese chinese = new Chinese(); for (int i = ; i < o1.length() && i < o2.length(); i++) {
int codePoint1 = o1.codePointAt(i);
int codePoint2 = o2.codePointAt(i);
if (codePoint1 == codePoint2)
continue; int stroke1 = chinese.stroke(codePoint1);
int stroke2 = chinese.stroke(codePoint2); if (stroke1 < || stroke2 < ) {
return codePoint1 - codePoint2;
} if (stroke1 != stroke2) {
return stroke1 - stroke2;
}
} return o1.length() - o2.length();
}
}
笔画比较器测试
/**
* @author Jeff
*
* Copyright (c) 复制或转载本文,请保留该注释。
*/
package chinese.utility.test; import java.util.Comparator; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import chinese.utility.StrokeComparator; public class StrokeComparatorTest { private Comparator<String> comparator; @Before
public void setUp() {
comparator = new StrokeComparator();
} /**
* 相同笔画数
*/
@Test
public void testCompareEquals() {
Assert.assertTrue(comparator.compare("一", "丨") == );
} /**
* 不同笔画数
*/
@Test
public void testCompare() {
Assert.assertTrue(comparator.compare("一", "二") < );
Assert.assertTrue(comparator.compare("唔", "马") > );
} /**
* 长度不同
*/
@Test
public void testCompareDefficultLength() {
Assert.assertTrue(comparator.compare("二", "二一") < );
} /**
* 非汉字的比较
*/
@Test
public void testABC() {
Assert.assertTrue(comparator.compare("一", "a") > );
Assert.assertTrue(comparator.compare("a", "b") < );
}
}
笔画比较器
/**
* @author Jeff
*
* Copyright (c) 复制或转载本文,请保留该注释。
*/
package chinese.utility.test; import java.util.Comparator; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import chinese.utility.StrokeComparator; public class StrokeComparatorTest { private Comparator<String> comparator; @Before
public void setUp() {
comparator = new StrokeComparator();
} /**
* 相同笔画数
*/
@Test
public void testCompareEquals() {
Assert.assertTrue(comparator.compare("一", "丨") == );
} /**
* 不同笔画数
*/
@Test
public void testCompare() {
Assert.assertTrue(comparator.compare("一", "二") < );
Assert.assertTrue(comparator.compare("唔", "马") > );
} /**
* 长度不同
*/
@Test
public void testCompareDefficultLength() {
Assert.assertTrue(comparator.compare("二", "二一") < );
} /**
* 非汉字的比较
*/
@Test
public void testABC() {
Assert.assertTrue(comparator.compare("一", "a") > );
Assert.assertTrue(comparator.compare("a", "b") < );
}
}
其他程序的汉字排序
Microsoft在这方面做得比较好。如Sql server 2000,Word和Excel都能按拼音和笔画排序。而Oracle只能是采取宽松拼音排序法。