Java中文排序

摘要:在Java中,对一个数组或列表(在本文中统称为集合)中的元素排序,是一个很经常的事情。好在Sun公司在Java库中实现了大部分功能。如果集合中的元素实现了Comparable接口,调用Array或Collections的静态(static)方法sort,就可以直接对集合排序。程序员用不同的方式实现了Comparator接口,就可以用各自不同的方式排序。对于包含汉字的字符串来说,排序的方式主要有两种:一种是拼音,一种是笔画。本文就讲述如何实现这两种不同的比较器(Comparator)。

在Java中,对一个数组或列表(在本文中统称为集合)中的元素排序,是一个很经常的事情。好在Sun公司在Java库中实现了大部分功能。如果集合中的元素实现了Comparable接口,调用以下的静态(static)方法,就可以直接对集合排序。

//数组排序方法//数组中的元素可以是像int这样的原生类型(primitivetype),也可以是像String这样实现了Comparable接口的类型,这里用type表示。java.util.Arrays.sort(type[]a);

//列表publicstaticvoidsort(Listlist)

以上的这些排序方式能满足大部分应用。但集合中的元素没有实现Comparable接口,或者集合中的元素要按一种特别的方式排序,这要怎么办?Sun公司早就想到了,并在Java库中提供上面两个方法的重载。

//数组排序方法。//数组中的元素可以是像int这样的原生类型(primitivetype),也可以是像String这样实现了Comparable接口的类型,这里用type表示。publicstaticvoidsort(T[]a,Comparatorc)

//列表publicstaticvoidsort(Listlist,Comparatorc)

只要实现了Comparator接口,就可以按程序员自己的意思去排序了。对于包含汉字的字符串来说,排序的方式主要有两种:一种是拼音,一种是笔画。汉字是通过一定的编码方式存储在计算机上的,主要的编码有:Unicdoe、GB2312和GBK等。

Unicode中编码表分为两块,一个是基本的,一个是辅助的。现在的大多数操作系统还不支持Unicode中辅助区域中的文字,如WinXp。

在Java中的字符就是Unicode码表示的。对于Unicode基本区域中的文字,用两个字节的内存存储,用一个char表示,而辅助区域中的文字用4个字节存储,因此辅助区域中的就要用两个char来表示了(表一种蓝色底就是辅助区域中的文字)。一个文字的unicode编码,在Java中统一用codePoint(代码点)这个概念。

中文和日文、韩文一样是表意文字,在Unicode中,中日韩三国(东亚地区)的文字是统一编码的。CJK代表的就是中日韩。在这里,我把这3中文字,都作为汉字处理了。(日语和韩语可能就是从汉语中衍生的吧!)

汉字在Unicode中的分布大致如下表:

在这些编码区间,有些编码是保留的。

GB2312是中华人民共和国最早的计算机汉字编码方式。大概有6000多个汉字,这些汉字是按拼音顺序编码的。这6000多个汉字都是简体中文字。

GB2312的扩展,并兼容GB2312。扩展后的汉字大概有2万多个,其中有简体汉字也有繁体汉字。

原理:汉字最早是GB2312编码,收录了六千多个汉字,是按拼音排序的,编码是连续的。后来出现了GBK编码,对GB2312进行了扩展,到了两万多汉字,并且兼容GB2312,也就是说GB2312中的汉字编码是原封不动搬到GBK中的(在GBK编码中[B0-D7]区中)。

如果我们只关心这6000多个汉字的顺序,就可以用下面的方法实现汉字宽松排序。

packagechinese.utility;

importjava.text.Collator;importjava.util.Comparator;importjava.util.Locale;

publicclassPinyinSimpleComparatorimplementsComparator{publicintcompare(Stringo1,Stringo2){returnCollator.getInstance(Locale.CHINESE).compare(o1,o2);}}

在对[孙,孟,宋,尹,廖,张,徐,昆,曹,曾,怡]这几个汉字排序,结果是:[曹,昆,廖,孟,宋,孙,徐,尹,曾,张,怡]。最后一个怡有问题,不该排在最后的。

注意:这个程序有两个不足

下面的测试代码可以证明

/***非常用字(怡)*/@TestpublicvoidtestNoneCommon(){Assert.assertTrue(comparator.compare("怡","张")>0);}/***同音字*/@TestpublicvoidtestSameSound(){Assert.assertTrue(comparator.compare("怕","帕")!=0);}

实现代码:

importjava.util.Comparator;importnet.sourceforge.pinyin4j.PinyinHelper;

publicclassPinyinComparatorimplementsComparator{

publicintcompare(Stringo1,Stringo2){

for(inti=0;i

intcodePoint1=o1.charAt(i);intcodePoint2=o2.charAt(i);

if(Character.isSupplementaryCodePoint(codePoint1)||Character.isSupplementaryCodePoint(codePoint2)){i++;}

if(codePoint1!=codePoint2){if(Character.isSupplementaryCodePoint(codePoint1)||Character.isSupplementaryCodePoint(codePoint2)){returncodePoint1-codePoint2;}

Stringpinyin1=pinyin((char)codePoint1);Stringpinyin2=pinyin((char)codePoint2);

if(pinyin1!=null&&pinyin2!=null){//两个字符都是汉字if(!pinyin1.equals(pinyin2)){returnpinyin1.compareTo(pinyin2);}}else{returncodePoint1-codePoint2;}}}returno1.length()-o2.length();}

/***字符的拼音,多音字就得到第一个拼音。不是汉字,就returnnull。*/privateStringpinyin(charc){String[]pinyins=PinyinHelper.toHanyuPinyinStringArray(c);if(pinyins==null){returnnull;}returnpinyins[0];}}

测试:

importjava.util.Comparator;

importorg.junit.Assert;importorg.junit.Test;

importchinese.utility.PinyinComparator;

publicclassPinyinComparatorTest{

privateComparatorcomparator=newPinyinComparator();

/***常用字*/@TestpublicvoidtestCommon(){Assert.assertTrue(comparator.compare("孟","宋")<0);}

/***不同长度*/@TestpublicvoidtestDifferentLength(){Assert.assertTrue(comparator.compare("他奶奶的","他奶奶的熊")<0);}

/***和非汉字比较*/@TestpublicvoidtestNoneChinese(){Assert.assertTrue(comparator.compare("a","阿")<0);Assert.assertTrue(comparator.compare("1","阿")<0);}

/***非常用字(怡)*/@TestpublicvoidtestNoneCommon(){Assert.assertTrue(comparator.compare("怡","张")<0);}

/***同音字*/@TestpublicvoidtestSameSound(){Assert.assertTrue(comparator.compare("怕","帕")==0);}

/***多音字(曾)*/@TestpublicvoidtestMultiSound(){Assert.assertTrue(comparator.compare("曾经","曾迪")>0);}

}

我的这样严格的拼音排序还是有有待改进的地方,看上面测试代码的最后一个测试,就会发现:程序不会根据语境来判断多音字的拼音,仅仅是简单的取多音字的第一个拼音。

要按笔画排序,就要实现笔画比较器。

classStokeComparatorimplementsComparator

如果有个方法可以求得汉字的笔画数,上面的功能就很容易实现。如何求一个汉字的笔画数?最容易想到的就是查表法。建一个汉字笔画数表,如:

如果是连续的、按unicode编码排好顺序的表,实际存储在笔画数表中的只需最后一列就够了。

那如何建这个表呢?这个表存储在哪里?

现在大多数系统还只能支持Unicode中的基本汉字那部分汉字,编码从U9FA6-U9FBF。所以我们只建这部分汉字的笔画表。汉字笔画数表,我们可以按照下面的方法生成:

importjava.io.IOException;importjava.io.PrintWriter;

publicclassChineseCoder{

publicstaticvoidmain(String[]args)throwsIOException{PrintWriterout=newPrintWriter("Chinese.csv");//基本汉字for(charc=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程序分析吧。

importjava.io.File;importjava.io.IOException;importjava.io.PrintWriter;importjava.util.Scanner;

publicclassStroke{

/***@paramargs*@throwsIOException*/publicstaticvoidmain(String[]args)throwsIOException{Scannerin=newScanner(newFile("Chinese.csv"));PrintWriterout=newPrintWriter("ChineseStroke.csv");StringoldLine="999999";intstroke=0;while(in.hasNextLine()){Stringline=in.nextLine();if(line.compareTo(oldLine)<0){stroke++;}oldLine=line;out.println(line+","+stroke);}out.flush();out.close();in.close();}

上面用的这个规律有问题吗?有问题,从ChineseStroke.csv文件抽取最后几个汉字就发现,笔画数不对。为什么呢?

我们要人工核对ChineseStroke文件,但只要核对在笔画变化的那几个汉字的笔画数。最后,我发现,只有笔画数多于30的少数几个汉字的笔画数不对。核对并矫正笔画数后,用Excel按Unicode重新排序,去掉汉字和Unicode两列,只保留笔画数那列,得到Stroke.csv文件。

importstaticorg.junit.Assert.assertEquals;

importorg.junit.Before;importorg.junit.Test;importchinese.utility.Chinese;

publicclassStrokeTest{

Chinesechinese;

@BeforepublicvoidsetUp(){chinese=newChinese();}

@TestpublicvoidtestStroke(){assertEquals(1,chinese.stroke(‘一‘));}

@TestpublicvoidtestStroke2(){assertEquals(2,chinese.stroke(‘二‘));}

@TestpublicvoidtestStroke16(){assertEquals(16,chinese.stroke(‘龍‘));}

@TestpublicvoidtestStrokeABC(){assertEquals(-1,chinese.stroke(‘a‘));}

publicclassStrokeComparatorimplementsComparator{

Chinesechinese=newChinese();

for(inti=0;i

intstroke1=chinese.stroke(codePoint1);intstroke2=chinese.stroke(codePoint2);

if(stroke1<0||stroke2<0){returncodePoint1-codePoint2;}

if(stroke1!=stroke2){returnstroke1-stroke2;}}

returno1.length()-o2.length();}}

importorg.junit.Assert;importorg.junit.Before;importorg.junit.Test;

importchinese.utility.StrokeComparator;

publicclassStrokeComparatorTest{

privateComparatorcomparator;@BeforepublicvoidsetUp(){comparator=newStrokeComparator();}

/***相同笔画数*/@TestpublicvoidtestCompareEquals(){Assert.assertTrue(comparator.compare("一","丨")==0);}/***不同笔画数*/@TestpublicvoidtestCompare(){Assert.assertTrue(comparator.compare("一","二")<0);Assert.assertTrue(comparator.compare("唔","马")>0);}/***长度不同*/@TestpublicvoidtestCompareDefficultLength(){Assert.assertTrue(comparator.compare("二","二一")<0);}/***非汉字的比较*/@TestpublicvoidtestABC(){Assert.assertTrue(comparator.compare("一","a")>0);Assert.assertTrue(comparator.compare("a","b")<0);}}

Microsoft在这方面做得比较好。如Sqlserver2000,Word和Excel都能按拼音和笔画排序。而Oracle只能是采取宽松拼音排序法。

THE END
1.C++现在,我们来深入了解一些 C++ 代码以实现这些 Unicode UTF-8/UTF-16 编码转换。为实现此目标,有两个关键 Win32 API 可以使用: MultiByteToWideChar 和 its symmetric WideCharToMultiByte。可以调用前者以从 UTF-8(特定 API 术语中的“多字节”字符串)转换为 UTF-16(“宽字符”字符串);后者可用于反向转换。因为这https://msdn.microsoft.com/zh-cn/magazine/mt763237.aspx
2.C++编码最全详解c++编码类型 所属专栏:C++深入学习笔记 欢迎来到我的学习笔记! 一、什么编码? 编码:人类文字信息是有各种各样的符号组成的, 但是他们却不能直接在内存中存储。计算机内存里面只存储二进制信息0、1。无符号整型:0 ~ 255共256个收据;有符号整型:-128 ~ 127。那么内存中如何表示符号? 二、各种编码方式 2.1 ASCII编https://blog.csdn.net/lusanjiu/article/details/143085301
3.C++中文字符相关应用方法详解1.汉字编码方式的介绍 对英文字符的处理,7位ASCII码字符集中的字符即可满足使用需求,且英文字符在计算机上的输入及输出也非常简单,因此,英文字符的输入、存储、内部处理和输出都可以只用同一个编码(如ASCII码)。 而在C++中文字符处理中,汉字是一种象形文字,字数极多(现代汉字中仅常用字就有六、七千个,总字数高达https://www.51cto.com/article/182437.html
4.C/C++对Unicode编码的处理zhpacer一直都觉得C++对Unicode编码的处理比较麻烦,现就个人经验对这一方面做个总结。 Unicode编码 在领域中,Unicode(统一码、万国码、单一码、标准万国码)是业界的一种标准,它可以使电脑得以呈现世界上数十种文字的系统。Unicode是基于(Universal Character Set)的标准来发展,并且同时也以书本的形式(The Unicode Standard,目前http://blog.chinaunix.net/uid-23414687-id-2425175.html
5.一段确认中文汉字编码的代码gallop博客在遇到中文汉字乱码的时候,我们经常需要确认当前汉字的编码以及需要转换成哪种编码,以下代码能很好的协助我们进行汉字的转码: public static void printbytes(byte[] bytes){ int j = 0; String s = ""; int len = bytes.length; System.out.println ..https://www.iteye.com/blog/793940
6.运用设计模式设计MIME编码类C++MFCMIME编码的原理就是把 8 bit 的内容转换成 7 bit 的形式以能正确传输,在接收方收到之后,再将其还原成 8 bit 的内容。对邮件进行编码最初的原因是因为 Internet 上的很多网关不能正确传输8 bit 内码的字符,比如汉字等。MIME编码共有Base64、Quoted-printable、7bit、8bit和Binary等几种。 http://www.jzxx.wj.czedu.cn/html/article4420916.html
7.在C++,C#中是不是都是ASCII编码,那么为什么能输出汉字呢?不是ASCII编码,依你保存的文件格式而定,如果你的文件是UNICODE格式保存,那汉字就是UNICODE编码保存,如果你的文件是GB-2312格式,那汉字就是GB-2312编码保存 使用汉字不需要事先声明,不过需要操作系统支持,如果系统不支持汉字,比如在英文Windows上(没安装中文字体)运行,那么中文都会变成方框或乱码.https://zhidao.baidu.com/question/37834079.html
8.C++char类型和输入输出优化C语言当我们把一个字符赋值给char型变量的时候,它会去查ASCII表,找到字符对应的编号。同样,当我们使用%c输出一个字符的时候,它也会去寻找char中存储的编码对应的符号进行输出。 既然字符在C++当中都是以数字的形式存储的,那么我们就可以对它来进行加减运算。 https://m.jb51.net/article/227112.htm
9.GB2312汉字编码字符集对照表BeJSON.comGB2312汉字编码字符集对照表,GB2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时,GB 2312收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。https://www.bejson.com/document/gb2312/
10.c++C++如何从将遍历带中文的字符串呢?附带一些有关链接 unicode编码中一个汉字占几个字节? 汉字Unicode 编码范围 C++输出中文字符(转) c++utf-8编码 有用关注2收藏 回复 阅读5.5k 提及: static int num = 2; hans[num] = ch; --num; if( !num ){ // 当 num 为0时 bug? 回复2020-08-19 https://segmentfault.com/q/1010000023681498/
11.字节码:ASCII编码:单字节编码,ANSI编码:多字节编码,UNICODE编码④UTF-16编码中,一个英文字母字符或一个汉字字符存储都需要2个字节(Unicode扩展区的一些汉字存储需要4个字节)。 ⑤UTF-32编码中,世界上任何字符的存储都需要4个字节。 ANSI编码有很多种,但是都只是规定自己国家的语言,这时候出现了UNICODE编码,该编码类似于ANSI,使用多个字节表示一个字符,UNICODE编码把世界上各种主要https://cloud.tencent.com/developer/article/1065253
12.软件代码编码标准规范(c++)v1.0.doc软件代码编码标准规范(c++)-v1.0.doc,SF-研发中心-** 第1.0版 PAGE 2016-1-1发布 2016-1-1实施 成都天锐星通有限公司 第 PAGE 4 页共 NUMPAGES 43 页 天锐星通软件规范v1.0 (C++版本) 编制 日期 审核 日期 批准 日期 目的 良好的编程风格是提高程序可靠性非常重要的手段https://m.book118.com/html/2023/1118/8124072115006006.shtm