java中文排序问题(转)

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

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

// 列表
public static <T> void sort(List<T> list)

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

// 数组排序方法。
// 数组中的元素可以是像int这样的原生类型(primitive type), 也可以是像String这样实现了Comparable接口的类型,这里用type表示。 
public static <T> void sort(T[] a, Comparator<? super T> c)

// 列表
public static <T> void sort(List<T> list, Comparator<? super T> c)

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

Unicode 编码中的汉字

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

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

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

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

  首字编码 尾字编码 个数
基本汉字 U4E00 U9FBF 20928
异性字 UF900 UFAFF 512
扩展A U3400 U4D8F 512
扩展B U20000 U2A6DF 42720
补充 U2F800 U2FA1F 544
其他     ...

表一

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

GB2312编码

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

GBK编码

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

拼音排序

拼音有好几种方式,其中最主要的是中华人民共和国的汉语拼音 Chinese Phonetic。对汉字的排序有两种:一种是宽松的,能够拼音排序最常用的汉字,另一种是严格的,能够拼音排序绝大部分大部分汉字。

宽松的拼音排序法

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

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

/**
* @author Jeff 

* Copyright (c) 复制或转载本文,请保留该注释。
*/

package chinese.utility;

import java.text.Collator; 
import java.util.Comparator; 
import java.util.Locale;

public class PinyinSimpleComparator implements Comparator<String> { 
    public int compare(String o1, String o2) { 
        return Collator.getInstance(Locale.CHINESE).compare(o1, o2); 
    } 
}

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

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

由于gb2312中的汉字编码是连续的,因此新增加的汉字不可能再照拼音顺序插入到已有的gb2312编码中,所以新增加的汉字不是拼音顺序排的。 同音字比较的结果不等于0 。

下面的测试代码可以证明

/**
* @author Jeff 

* Copyright (c) 复制或转载本文,请保留该注释。
*/

/**
* 非常用字(怡)
*/
@Test
public void testNoneCommon() {
    Assert.assertTrue(comparator.compare("怡", "张") > 0); 
}

/**
* 同音字
*/
@Test
public void testSameSound() {
    Assert.assertTrue(comparator.compare("怕", "帕") != 0); 
}

严格的拼音排序法

为了解决宽松的拼音的两点不足,可以通过实现汉语拼音的函数来解决。goolge下看到sf上有个pinyin4j的项目,可以解决这个问题,pinyin4j的项目地址是:http://pinyin4j.sourceforge.net/

实现代码:

/**
  * @author Jeff 
  * 
  * Copyright (c) 复制或转载本文,请保留该注释。
  */
package chinese.utility;

import java.util.Comparator;
import net.sourceforge.pinyin4j.PinyinHelper;

public class PinyinComparator implements Comparator<String> {

    public int compare(String o1, String o2) {

        for (int i = 0; i < o1.length() && i < o2.length(); i++) {

            int codePoint1 = o1.charAt(i);
            int codePoint2 = o2.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 o1.length() - o2.length();
    }

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

测试:

/**
  * @author Jeff 
  * 
  * Copyright (c) 复制或转载本文,请保留该注释。
  */
package chinese.utility.test;

import java.util.Comparator;

import org.junit.Assert;
import org.junit.Test;

import chinese.utility.PinyinComparator;

public class PinyinComparatorTest {

    private Comparator<String> comparator = new PinyinComparator();

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

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

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

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

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

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

}

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

笔画排序

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

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 = "999999";
        int stroke = 0;
        while (in.hasNextLine()) {
            String line = in.nextLine();
            if (line.compareTo(oldLine) < 0) {
                stroke++;                
            }
            oldLine = line;
            out.println(line + "," + stroke);            
        }
        out.flush();
        out.close();
        in.close();
    }

}

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

笔画数可能不是连续的。 n+1笔画数的最小Unicode码可能比n笔画数的最大Unicode码要大

我们要人工核对ChineseStroke文件,但只要核对在笔画变化的那几个汉字的笔画数。最后,我发现,只有笔画数多于30的少数几个汉字的笔画数不对。核对并矫正笔画数后,用ExcelUnicode重新排序,去掉汉字和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(1, chinese.stroke('一'));
    }

    @Test
    public void testStroke2() {
        assertEquals(2, chinese.stroke('二'));
    }

    @Test
    public void testStroke16() {
        assertEquals(16, chinese.stroke('龙'));
    }

    @Test
    public void testStrokeABC() {
        assertEquals(-1, 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 = 0; 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 < 0 || stroke2 < 0) {
                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("一", "丨") == 0);
    }
    /**
     * 不同笔画数
     */
    @Test
    public void testCompare() {
        Assert.assertTrue(comparator.compare("一", "二") < 0);
        Assert.assertTrue(comparator.compare("唔", "马") > 0);
    }
    /**
     * 长度不同
     */
    @Test
    public void testCompareDefficultLength() {
        Assert.assertTrue(comparator.compare("二", "二一") < 0);
    }
    /**
     * 非汉字的比较
     */
    @Test
    public void testABC() {
        Assert.assertTrue(comparator.compare("一", "a") > 0);
        Assert.assertTrue(comparator.compare("a", "b") < 0);        
    }
}

笔画比较器

/**
  * @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("一", "丨") == 0);
    }
    /**
     * 不同笔画数
     */
    @Test
    public void testCompare() {
        Assert.assertTrue(comparator.compare("一", "二") < 0);
        Assert.assertTrue(comparator.compare("唔", "马") > 0);
    }
    /**
     * 长度不同
     */
    @Test
    public void testCompareDefficultLength() {
        Assert.assertTrue(comparator.compare("二", "二一") < 0);
    }
    /**
     * 非汉字的比较
     */
    @Test
    public void testABC() {
        Assert.assertTrue(comparator.compare("一", "a") > 0);
        Assert.assertTrue(comparator.compare("a", "b") < 0);        
    }
}

 

http://blog.csdn.net/hsliwei/article/details/7020628

 

 

时间: 2024-11-10 00:26:24

java中文排序问题(转)的相关文章

关于Java中文问题的几条分析原则

问题|中文   引言 尽管对于Java中文处理问题的讨论已不乏其数,但由于Java技术涉及内容广(J2EE包含了十几种相关技术),技术供应商繁多,面向Java的Web服务器.应用服务器以及JDBC数据库驱动等都没有官方的标准,所以Java应用在处理中文过程中出了存在固有的问题外也存在随着选用的服务器,驱动程序的不同而带来的Java中文问题的多变性,增加了问题的复杂度.那么,我们如何在这么纷繁的现象中找到问题的症结呢? Java中文问题的一般解决办法 事实上,Java的中文问题都是由于Java应用

Java中文问题及最优解决方法

解决|问题|中文 1.中文问题的来源 计算机最初的操作系统支持的编码是单字节的字符编码,于是,在计算机中一切处理程序最初都是以单字节编码的英文为准进行处理.随着计算机的发展,为了适应世界其它民族的语言(当然包括我们的汉字),人们提出了UNICODE编码,它采用双字节编码,兼容英文字符和其它民族的双字节字符编码,所以,目前,大多数国际性的软件内部均采用UNICODE编码,在软件运行时,它获得本地支持系统(多数时间是操作系统)默认支持的编码格式,然后再将软件内部的UNICODE转化为本地系统默认支持

java中文解决大全(下)

解决|中文 4.中文问题的分类及其建议最优解决办法     了解以上JAVA处理文件的原理之后,我们就可以提出了一套建议最优的解决汉字问题的办法.    我们的目标是:我们在中文系统中编辑的含有中文字符串或进行中文处理的JAVA源程序经编译后可以移值到任何其它的操作系统中正确运行,或拿到其它操作系统中编译后能正确运行,能正确地传递中文和英文参数,能正确地和数据库交流中英文字符串.    我们的具体思路是:在JAVA程序转码的入口和出口及JAVA程序同用户有输入输出转换的地方限制编码方法使之正确即

java中文解决大全(上)

解决|中文 说明:本文为作者原创,作者联系地址为:josserchai@yahoo.com.由于Java编程中的中文问题是一个老生常谈的问题,在阅读了许多关于Java中文问题解决方法之后,结合作者的编程实践,我发现过去谈的许多方法都不能清晰地说明问题及解决问题,尤其是跨平台时的中文问题.于是我给出此篇文章,内容包括对控制台运行的class.Servelets.JSP及EJB类中的中文问题我剖析和建议解决办法.希望大家指教. Abstract:本文深入分析了Java程序设计中Java编译器对jav

Java 中文问题的解决

解决|问题|中文 Java 中文问题一直困扰许多学习者.总结了下面的一些情况的解决方法.希望对大家有帮助. 连接 Mysql Database Server:------------------------------------------------------------------------------- mysql 不支持 unicode,所以比较麻烦. 将 connectionString 设置成 encoding 为 gb2312 String connectionString

Java中文问题详解,底层编码解剖

编码|问题|详解|中文 Java中文问题详解预备知识: 1.字节和unicode Java内核是unicode的,就连class文件也是,但是很多媒体,包括文件/流的保存方式 是使用字节流的. 因此Java要对这些字节流经行转化.char是unicode的,而byte是字节. Java中byte/char互转的函数在sun.io的包中间有.其中ByteToCharConverter类是中调度, 可以用来告诉你,你用的Convertor.其中两个很常用的静态函数是 public static By

关于java 快速排序问题 这个输出报错 我认为 while 有问题 求大神指导

问题描述 关于java 快速排序问题 这个输出报错 我认为 while 有问题 求大神指导 package QuickSort; import java.util.ArrayList; public class QuickSort { public static void main(String[] args) { ArrayList<Integer> lists = new ArrayList<Integer>(); lists.add(5); lists.add(2); lis

关于java中文语音合成的问题。

问题描述 关于java中文语音合成的问题. 本人要用java实现语音播报,但是找不到java的语音合成包.现跪求各位大神给一些建议. 解决方案 java默认能播放一些midi文件,这样的代码很好找,都是老代码了,要是想播放flc,或者MP3,或其他8和旋以上的文件,你搜搜gameframework,好像是oracle出的. 解决方案二: 我是想输入一个中文字符串,然后把这个字符串翻译成语音输出.但是找不到语音包.

java 集合排序问题,谢谢指导。

问题描述 java 集合排序问题,谢谢指导. 有一串分数我存在了rationalList集合里,然后要求对分数按倒数大小对分数进行排序 ListrationalList = new ArrayList<>(); Collections.sort(rationalList,new InverseComparator()); public class InverseComparator implements Comparator { // 比较两个分数倒数的大小 @Override public