仿联系人的排序

本来想模仿做一个联系人的快速索引,在敲代码做测试的过程中,想对读取文件获取的联系人进行排序,结果发现有许多排序上的问题。
于是,转移注意力到排序的方法上来,发现确实是有很多需要考虑的东西。

下面就把我在调试这个排序方法的过程给展示一下,说明一下排序中要考虑的方方面面。

1,参考网上部分人提供的方式,是默认的字符顺序来排序

这也是最容易想到的方法,就是一个按字符串的升序排列:
我将名字存放在一个list中:

List<String> listName;
在添加了联系人名之后,来进行排序:
    Collections.sort(listName, new SortComparator());

    /**
     * 按字符升序排序,最初的排序方法
     * */
    class SortComparator implements Comparator {
        @Override
        public int compare(Object t1, Object t2) {
            String a=(String)t1;
            String b=(String)t2;
            int flag=a.compareTo(b);
            if (flag==0){
                return a.compareTo(b);
            }else{
                return flag;
            }
        }
    };

然而,这种排序,对于我们中国人来说,不太合适,我们的名字,当然是使用汉字啦。虽然近来也有用英文名的,毕竟还是少数嘛。而汉字按默认的字符顺序排序,拼音相近的姓氏,排序却相差很远。
为什么会这样呢?
我们来简单了解下字符集:
Java使用Unicode字符编码集,它不是按照拼音顺序,基本上是按照CJK编码顺序。
什么是CJK ?其实是Chinese, Japanese, Korean 的缩写啦,叫做 中日韩统一表意文字(CJK Unified Ideographs)。是采用字根分解与合成的方法,简单讲,就是按类似偏旁笔画的划分来排序,而不是按拼音排序的。
所以,我们需要做下一步的工作:

2,对于汉字,转拼音,按字母排序

这里使用的是一个库:pinyin4j-2.5.0.jar
pinyin4j的官方下载地址:http://sourceforge.net/projects/pinyin4j/files/,目前最新的版本就是2.5.0。
库的使用比较简单,就是根据汉字,获取拼音,例如这样:

String[] pinyinArray =PinyinHelper.toHanyuPinyinStringArray('解');

为什么返回的是一个字符串数组?因为有多音字啊。
既然说到多音字,就需要选择恰当的读音了。这里采用的是一种比较简单的替换法,就是遇到多音字,转换为我们期望的一个同音的单音字。可以做成一个列表,遇到哪些,就更新进去,也还算方便。
还要考虑一种情况,就是拼音相同,字不同的,不能当成同样的字符来处理,否则会出现这样的排序:
许戈辉,徐静蕾,许晴。
是不是感觉很奇怪,同姓的居然没有挨在一块。所以,在比较到拼音相同时,要继续比较原来的汉字是否相同。对于这一步的汉字比较,我是直接使用默认的字符顺序比较的,就是区分开同音字就行了,没有继续考虑笔画顺序什么的。

3,要按中国人的习惯来,姓按拼音首字母分段,同一段内汉字优先

我们首先关注的是第一个字,也就是姓,按拼音的首字母分割。
对于汉字拼音与英文字母相同的情况,例如:白,B,如何排序?
按首字母分割后,在同一个段内(同一个首字母的情况下),汉字在前,英文字母在后。

4,不是第一个字符时,汉字在所有的字母之前

这一条和第3条很容易混淆,最大的区别就在于,第一个字符,作为姓氏,要按首字母来进行分段,而后面的字符不需要考虑这点。
举个例子,有3个联系人:白雪,Baby,崔健。则白雪和Baby这两个联系人要排在B这个段内,而崔健排中C段,所以Baby排中崔健之前。排序为:白雪,Baby,崔健。
又有3个联系人:杨白雪,杨Baby,杨崔健。由于姓是相同的,后面考虑名字时,先排汉字,后排英文字母,所以杨崔健排在杨Baby之前。排序为:杨白雪,杨崔健,杨Baby。

5,排序:汉字、字母、数字、其他

确定了汉字和字母之后,也考虑下其他字符的情况。这里我也没有进行很细致的区分,只是划分成4类:最常用的是汉字,然后外国人使用英文字母,还有就是考虑了数字也会有使用的情况,再剩下的就没有区分了,都当做是“其他”这一项。其排序的优先级,从前面的描述中已经体现了,就是:汉字>字母>数字>其他字符。

6,姓名前后的空格,要去掉

之所以特别提出来说明,是因为我们可能在输入联系人时,不小心就输入了空格。总之,我用我手机中的联系人进行测试时,就遇到了这个问题。由于空格不在26个字母的范围类,搞得那个联系人排到所有字母段之外去了,当时还奇怪了好一会呢。
当然知道了原因,解决起来也很简单,就是调用一次trim()就好了。

7,怎么来比较所有字符

知道了这么多,似乎还是没有想明白,到底怎么进行一个完整的排序。要知道,一个人的名字,可能有好几个字呢。特别是,汉字、英文字母、数字、其他字符混合排序的,虽然概率小,但是我们写程序的人,要都考虑进来呀。
还有拼音与英文字母,以及字符个数不定,到底要判断几次呢,想想就觉得好麻烦啊!

在经过一段时间的混乱后,我忽然想到一个方法,就是递归!
既然能比较好一个字符,就可以递归调用,来比较多个字符,直到某一个字符串结束。
一个汉字的拼音,与字母的比较,只在姓名中的第一个字符处使用。对于其他位置上的汉字与字母,使用更基础的判断(汉字>字母>数字>其他)就可以确定顺序了,根本还用不到拼音呢,只有在都是汉字时才用到。

8,名字中间的空格,不能去掉

这个问题,是在调试过程中发现的。是由于采用了递归,则判断名称中每一个字符时,就不能再使用trim了。所以trim只能最初确定名字时使用一次。

有了上面这么多考虑,基本上排序算法就梳理清楚了。

下面是实现:

   /**
     * 联系人排序,最终的排序方法
     * */
    private int compareCallNum=0;//判读是否是第一层的比较(递归调用中)
    class SortComparator implements Comparator {
        @Override
        public int compare(Object lhs, Object rhs) {

            compareCallNum = 0;
            return compareString((String)lhs,(String)rhs);

        }
    }

    //只比较一个字符,递归调用
    public int compareString(String lhs, String rhs) {

        compareCallNum++;

        //判断第一个字符,汉字最前,其次字母,然后是数字,若有其他符号,放在最后

        String nameA = lhs;//.trim();//注意,由于递归调用,所以此处不能再使用trim了
        String nameB = rhs;//.trim();

        //若存在长度为0的情况:
        if((nameA.length()==0)&&(nameB.length()==0)){
            return 0;
        } else if(nameA.length()==0){
            return -1;
        } else if(nameB.length()==0){
            return 1;
        }

        String firstStrA = nameA.substring(0,1);
        String firstStrB = nameB.substring(0,1);

        //先从类型上来区分:汉字>字母>数字>其他符号,若类型不同,立即出比较的结果
        //但是汉字与字母,由于存在首字母的分段,所以先不区分开
        int typeA = getFirstCharType(nameA);
        int typeB = getFirstCharType(nameB);
        if(typeA>typeB){
            LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
            return -1;//返回负值,则往前排
        } else if(typeA<typeB){
            LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
            return 1;
        }


        //类型相同,需要进行进一步的比较
        int compareResult ;

        //不是字母与汉字
        if(typeA<9 && typeB<9){
            compareResult = firstStrA.compareTo(firstStrB);
            if(compareResult!=0){
                //若不同,立即出来比较结果
                LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
                return compareResult;
            } else {
                //若相同,则递归调用
                return compareString(nameA.substring(1),nameB.substring(1));
            }
        }

        //是字母或汉字

        //若是首字母,先用第一个字母或拼音进行比较
        //否则,先判断字符类型
        String firstPinyinA = PinYinStringHelper.getFirstPingYin(nameA).substring(0, 1);
        String firstPinyinB = PinYinStringHelper.getFirstPingYin(nameB).substring(0, 1);
        if(compareCallNum==1) {
            compareResult = firstPinyinA.compareTo(firstPinyinB);
            if (compareResult != 0) {
                LogUtil.logWithMethod(new Exception(), "nameA=" + nameA + " nameB=" + nameB + " compareResult=" + compareResult);
                return compareResult;
            }
        }

        //若首字的第一个字母相同,或不是首字,判断原字符是汉字还是字母,汉字排在前面
        typeA = getFirstCharType2(nameA);
        typeB = getFirstCharType2(nameB);
        if(typeA>typeB){
            LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
            return -1;
        } else if(typeA<typeB){
            LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
            return 1;
        }

        //不是首字母,在字符类型之后判断,第一个字母或拼音进行比较
        if(compareCallNum!=1) {
            compareResult = firstPinyinA.compareTo(firstPinyinB);
            if (compareResult != 0) {
                LogUtil.logWithMethod(new Exception(), "nameA=" + nameA + " nameB=" + nameB + " compareResult=" + compareResult);
                return compareResult;
            }
        }

        if(isLetter(nameA)&&isLetter(nameB)) {
            //若是同一个字母,还要比较下大小写
            compareResult = firstStrA.compareTo(firstStrB);
            if (compareResult != 0) {
                LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB+" compareResult="+compareResult);
                return compareResult;
            }
        }

        if(isHanzi(nameA)&&isHanzi(nameB)) {
            //使用姓的拼音进行比较
            compareResult = PinYinStringHelper.getFirstPingYin(nameA).compareTo(PinYinStringHelper.getFirstPingYin(nameB));
            if (compareResult != 0) {
                LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
                return compareResult;
            }

            //若姓的拼音相同,比较汉字是否相同
            compareResult = firstStrA.compareTo(firstStrB);
            if (compareResult != 0) {
                LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
                return compareResult;
            }
        }
        //若相同,则进行下一个字符的比较(递归调用)
        return compareString(nameA.substring(1),nameB.substring(1));
    }

排序后的效果,上图:

这里写图片描述
说明:
第一张图展示了汉字与字母、数字、特殊符号的顺序;
第二张图展示了同音汉字的排序,以及同姓时英文字母排序在后的情况;
第三张图展示了以数字与特殊字符开头的联系人排列位置(就是最后啦);

完整的工程,见如下地址:

https://github.com/lintax2017/ContactListDemo

参考:

http://blog.csdn.net/zpp119/article/details/7976139
http://www.javaapk.com/topics/demo/5894.html
http://www.2cto.com/kf/201311/258190.html

猜你喜欢

转载自blog.csdn.net/lintax/article/details/77427486