本來想模仿做一個聯系人的快速索引,在敲代碼做測試的過程中,想對讀取檔案擷取的聯系人進行排序,結果發現有許多排序上的問題。
于是,轉移注意力到排序的方法上來,發現确實是有很多需要考慮的東西。
下面就把我在調試這個排序方法的過程給展示一下,說明一下排序中要考慮的方方面面。
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==){
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=;//判讀是否是第一層的比較(遞歸調用中)
class SortComparator implements Comparator {
@Override
public int compare(Object lhs, Object rhs) {
compareCallNum = ;
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()==)&&(nameB.length()==)){
return ;
} else if(nameA.length()==){
return -;
} else if(nameB.length()==){
return ;
}
String firstStrA = nameA.substring(,);
String firstStrB = nameB.substring(,);
//先從類型上來區分:漢字>字母>數字>其他符号,若類型不同,立即出比較的結果
//但是漢字與字母,由于存在首字母的分段,是以先不區分開
int typeA = getFirstCharType(nameA);
int typeB = getFirstCharType(nameB);
if(typeA>typeB){
LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
return -;//傳回負值,則往前排
} else if(typeA<typeB){
LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
return ;
}
//類型相同,需要進行進一步的比較
int compareResult ;
//不是字母與漢字
if(typeA< && typeB<){
compareResult = firstStrA.compareTo(firstStrB);
if(compareResult!=){
//若不同,立即出來比較結果
LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
return compareResult;
} else {
//若相同,則遞歸調用
return compareString(nameA.substring(),nameB.substring());
}
}
//是字母或漢字
//若是首字母,先用第一個字母或拼音進行比較
//否則,先判斷字元類型
String firstPinyinA = PinYinStringHelper.getFirstPingYin(nameA).substring(, );
String firstPinyinB = PinYinStringHelper.getFirstPingYin(nameB).substring(, );
if(compareCallNum==) {
compareResult = firstPinyinA.compareTo(firstPinyinB);
if (compareResult != ) {
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 -;
} else if(typeA<typeB){
LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
return ;
}
//不是首字母,在字元類型之後判斷,第一個字母或拼音進行比較
if(compareCallNum!=) {
compareResult = firstPinyinA.compareTo(firstPinyinB);
if (compareResult != ) {
LogUtil.logWithMethod(new Exception(), "nameA=" + nameA + " nameB=" + nameB + " compareResult=" + compareResult);
return compareResult;
}
}
if(isLetter(nameA)&&isLetter(nameB)) {
//若是同一個字母,還要比較下大小寫
compareResult = firstStrA.compareTo(firstStrB);
if (compareResult != ) {
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 != ) {
LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
return compareResult;
}
//若姓的拼音相同,比較漢字是否相同
compareResult = firstStrA.compareTo(firstStrB);
if (compareResult != ) {
LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
return compareResult;
}
}
//若相同,則進行下一個字元的比較(遞歸調用)
return compareString(nameA.substring(),nameB.substring());
}
排序後的效果,上圖:
說明:
第一張圖展示了漢字與字母、數字、特殊符号的順序;
第二張圖展示了同音漢字的排序,以及同姓時英文字母排序在後的情況;
第三張圖展示了以數字與特殊字元開頭的聯系人排列位置(就是最後啦);
完整的工程,見如下位址:
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