java的比較器小結

摘要

java的比較器分兩種,也即是內外比較器,內部比較器是comparable接口,外部比較器是comparator接口,為什么分內外比較器呢。

內部比較器:comparable,當需要對某個類的對象進行排序的時候,則需要該類實現comparable接口,重寫comparaTo方法,實現這個接口的類的對象列表,可使用Array.sort或者Collections.sort進行排序,

外部比較器:comparator接口,匿名內部類的形式存在,重寫內部的compare方法,可對對象數組或者集合使用Arrays.sort(數組,比較器)或者Collections.sort(集合,比較器)進行排序

compara和comparaTo方法共同之處,就是如果比較的數比被比較的數小返回-1,

例: a.comparaTo(b) a小,返回-1 ,a大返回1,compara同理

可以查看我的博客
https://itzmn.github.io/2018/11/16/java%E7%9A%84%E6%AF%94%E8%BE%83%E5%99%A8%E5%B0%8F%E7%BB%93/

Comparable

comparaTo方法

類實現comparable接口,都要重寫comparaTo方法,舉個栗子,

Integer類中的方法

//Integer實現了Comparable接口
public final class Integer extends Number implements Comparable<Integer> {

//比較兩個數值
public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
}
//比較兩個數值,如果前面一個大,返回1,小返回-1,相等返回0
public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

String類中

//實現了接口
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    //String類中的compareTo方法
public int compareTo(String anotherString) {
        //得到調用者的值的長度
        int len1 = value.length;
        //得到比較的值的長度
        int len2 = anotherString.value.length;
        //查看最小長度
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        //循環到最小長度的值結束
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            //比較每一個字符
            if (c1 != c2) {
                //返回字符的ASCII碼差值
                return c1 - c2;
            }
            k++;
        }
        //如果前面的字符都一樣,返回長度差
        return len1 - len2;
    }

以上是java內置好的一些類,實現了接口,重寫了方法,如果我們自己類想要實現接口,可以重寫這個方法,自定義排序的方法

例:

 Class B implements Comparable<B>{
     
     int comparaTo(B b){
        //return的值按照自己是想怎么排序進行返回
         return 
     }
 }

當我們的類實現了Comparable接口,那么類對象集合,或者數組可使用工具類進行排序。

可以使用

Arrays.sort(數組,null),Collections.sort(集合,null)進行排序

Comparator

comparator一般是外比較器,以匿名內部類的形式存在,對數據進行比較

可新建一個比較器的對象,實現比較器內部的compara方法

//新建一個比較器
new Comparator<Integer>(){
            @Override
            public int compare(Integer str1, Integer str2){
                return str1.compareTo(str2);
            }
};

可使用Arrays.sort(數組,比較器)或者Collections.sort(集合,比較器)進行排序

Collections.sort()

了解了比較器,我們看一下一些工具類內到底是如何將數組進行排序的

我們舉個栗子,跟蹤源碼看一下

模擬數據

10 14 7 8 12

ArrayList<Integer> integers = new ArrayList<>();
        integers.add(10);
        integers.add(14);
        integers.add(7);
        integers.add(8);
        integers.add(12);
        integers.add(3);
        Collections.sort(integers, new Comparator<Integer>(){
            @Override
            public int compare(Integer str1, Integer str2){

                return str1.compareTo(str2);
            }
        });

開始調用

我們點進源碼看一下

1.第一步

這是Collections的sort方法,調用了list.sort方法,

@SuppressWarnings({"unchecked", "rawtypes"})
    public static <T> void sort(List<T> list, Comparator<? super T> c) {
        list.sort(c);
    }

2.第二步

我們點進list.sort()方法,這里他將集合數據轉成了數組傳入后面。數組默認長度是10,但是只有size個數據。但是list是個接口,所以我們查看其中一個實現類的實現方法,ArrayList的實現方法,

//這是ArrayList的sort方法。
public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
    //調用了Arrays.sort 并將比較器傳了進來,
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
  }

3.第三步

我們點進Arrays.sort方法查看,我們一條一條看

public static <T> void sort(T[] a, int fromIndex, int toIndex,
                                Comparator<? super T> c) {
        if (c == null) {
            sort(a, fromIndex, toIndex);
        } else {
            rangeCheck(a.length, fromIndex, toIndex);
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, fromIndex, toIndex, c);
            else
                TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
        }
    }

1.如果比較器為null,這個我們一會再看,(記住這個,我們會回來的)

2.rangeCheck(),看名字就可以看出,這是一個檢驗下標是否越界的方法,就不細說了,

3.下面這個判斷,我也不大懂,但是每次他都是進入了else進行排序

4.第四步

我們進入TimSort.sort方法,傳入的數據是 a:數組數據,也就是集合中的數據,formIndex:0,toIndex:是剛剛集合的size。

 static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
                         T[] work, int workBase, int workLen) {
        assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted

        // If array is small, do a "mini-TimSort" with no merges
        if (nRemaining < MIN_MERGE) {
            int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
            binarySort(a, lo, hi, lo + initRunLen, c);
            return;
        }

//下面有些代碼,都沒看了,就看到這
 .....
 }

我們還是一行一行看

1.斷言,判斷下標是否正確

2.得到集合的數據長度,并判斷是否小于2,也即是判斷有沒有排序的必要

3.如果大于,判斷數據是否小于32,我們看的是小于,大于的代碼沒看

4.int initRunLen = countRunAndMakeAscending(a, lo, hi, c);

我們看一下這個方法是干什么的

下面點進入,貼代碼

4.1 countRunAndMakeAscending代碼
//傳入的參數,a是數組,lo是開始位置默認是0,hi是結束位置默認是剛剛集合的長度
private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,
                                                    Comparator<? super T> c) {
        assert lo < hi;
        int runHi = lo + 1;
        if (runHi == hi)
            return 1;

        // Find end of run, and reverse range if descending
        if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
           //判斷后面一個值是否是小于當前值的。
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
                runHi++;
            reverseRange(a, lo, runHi);
        } else {                              // Ascending
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
                runHi++;
        }

        return runHi - lo;
    }

解釋一下方法的參數

a是數組,lo是開始位置默認是0,hi是結束位置默認是剛剛集合的長度(下面稱集合長度),c是比較器

1.斷言,開始索引小于傳入的集合長度

2.runHi變成了開始坐標加一

3.如果這個數值和集合長度相同,則返回1,

4.判斷,并且將runHi加1,在這里我們剛剛在比較器內部的操作效果就顯示出來了。在這里,我們將坐標靠后面的數與該坐標數值進行比較,如果這里比較用的就是我們自己寫在比較器里面的比較

5.如果返回-1 ,說明后面一個值小,這兩個數構成了降序,然后還是接著判斷后面一個值和當前的值大小,直到降序被打斷,記錄這個坐標,一會返回這個坐標。

如果被打斷,進入另一個方法,將這一段降序的數組,反轉,改成升序,

// 反轉這部分的數組
private static void reverseRange(Object[] a, int lo, int hi) {
        hi--;
        while (lo < hi) {
        //交換數組的值
            Object t = a[lo];
            a[lo++] = a[hi];
            a[hi--] = t;
        }
    }

6.如果返回大于0,說明后面一個值大,這兩個值構成了升序。然后繼續比較runHi位置和前面一個位置的數值,如果都是后面的大,就一直向后,直到數值變化為小的,也即是一直是升序,突然升序變了,記錄下這個位置并返回這個位置。

4.1結束,回到上一個方法

5,第五步

我們返回了一個坐標,現在這個數組的開始到這個坐標以前,已經滿足了遞增的規律,然后我們需要將整個數據進行排序。進入了

binarySort(a, lo, hi, lo + initRunLen, c);方法

// 傳入數組,開始坐標,結束坐標,start是上一步,0加上已經排序好的坐標的后一位
private static <T> void binarySort(T[] a, int lo, int hi, int start,
                                       Comparator<? super T> c) {
        assert lo <= start && start <= hi;
        if (start == lo)
            start++;
        for ( ; start < hi; start++) {
            T pivot = a[start];

            // Set left (and right) to the index where a[start] (pivot) belongs
            int left = lo;
            int right = start;
            assert left <= right;
            
            while (left < right) {
                int mid = (left + right) >>> 1;
                if (c.compare(pivot, a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
            }
            assert left == right;

            int n = start - left;  // The number of elements to move
           
            switch (n) {
                case 2:  a[left + 2] = a[left + 1];
                case 1:  a[left + 1] = a[left];
                         break;
                default: System.arraycopy(a, left, a, left + 1, n);
            }
            a[left] = pivot;
        }
    }

首先我們看一下傳入的參數

// 傳入數組,lo開始坐標,hi結束坐標,start是上一步,0加上已經排序好的坐標的后一位

也就是說現在數組有hi個數據,但是從lo到start的數據是已經滿足遞增的數據了,前面已經看過了,現在我們需要將后面的數據和前面的數據匯合,形成整個遞增。

1.如果start==lo,說明前面沒有遞增數據,所以將坐標加1,為后面準備

2.循環,start以后都是尚未排序的,我們需要將后面的數據插入到前面的有序序列,

3.保存當前位置的數值,

4.得到已經遞增的數據的左坐標,也就是lo,得到遞增數據的右坐標也即是start

5.然后將當前位置的值與遞增數據的中間位置的數值進行比較,如果小于,那么說明當前值是插在遞增數據的前半段,將遞增數據的右坐標改成中間位置的坐標,如果是大于,那么是后半段,將遞增數據的左坐標改成中間位置,直到找到正確位置,判斷當前值的位置與正確位置的差,如果小與2則進行移動,如果大于則進行拷貝

6.循環將以上數值全部弄完,這樣我們就可以得到整個有序數列了。

我們現在回到第三步的第一小步,如果比較器為null,

進入sort(a, fromIndex, toIndex);

3.1

 //對沒有比較器的數組進行比較
 public static void sort(Object[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a, fromIndex, toIndex);
        else
            ComparableTimSort.sort(a, fromIndex, toIndex, null, 0, 0);
    }

3.2

//進入了ComparableTimSort的sort方法 ,傳入數組,起始坐標與結束坐標。
static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {
        assert a != null && lo >= 0 && lo <= hi && hi <= a.length;

        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted

        // If array is small, do a "mini-TimSort" with no merges
        if (nRemaining < MIN_MERGE) {
            int initRunLen = countRunAndMakeAscending(a, lo, hi);
            binarySort(a, lo, hi, lo + initRunLen);
            return;
        }

這里面的幾步如

int initRunLen = countRunAndMakeAscending(a, lo, hi);

binarySort(a, lo, hi, lo + initRunLen);

都與上面的幾步差不多,不過內部的比較方法不一樣

private static int countRunAndMakeAscending(Object[] a, int lo, int hi) {
        assert lo < hi;
        int runHi = lo + 1;
        if (runHi == hi)
            return 1;

        // Find end of run, and reverse range if descending
        if (((Comparable) a[runHi++]).compareTo(a[lo]) < 0) { // Descending
            while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) < 0)
                runHi++;
            reverseRange(a, lo, runHi);
        } else {                              // Ascending
            while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) >= 0)
                runHi++;
        }

        return runHi - lo;
    }

因為比較器為null,所以會將值轉成比較器進行比較,存儲的值,都是實現了Comparable接口的類,內部的比較方法也都重寫了,會按照自帶的比較。

在最后的比較階段也是一樣的。

private static void binarySort(Object[] a, int lo, int hi, int start) {
        assert lo <= start && start <= hi;
        if (start == lo)
            start++;
        for ( ; start < hi; start++) {
            Comparable pivot = (Comparable) a[start];

            // Set left (and right) to the index where a[start] (pivot) belongs
            int left = lo;
            int right = start;
            assert left <= right;
           
            while (left < right) {
                int mid = (left + right) >>> 1;
                if (pivot.compareTo(a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
            }
            assert left == right;

          
            int n = start - left;  // The number of elements to move
            // Switch is just an optimization for arraycopy in default case
            switch (n) {
                case 2:  a[left + 2] = a[left + 1];
                case 1:  a[left + 1] = a[left];
                         break;
                default: System.arraycopy(a, left, a, left + 1, n);
            }
            a[left] = pivot;
        }
    }

Arrays.sort

Arrays.sort在上面已經被調用過了,可以看一下

這樣我們就把比較器的代碼大致看完了 ,看這個源碼,我是傳數據debug看的,這樣數據改變更方面看一些,這里面我比較喜歡,是在數據排序的時候,這個是直接插入法,但是它優化了一點是,插入不是一個一個比較,而是和有序序列的中間值比較,這樣更快。看完之后我對比較器的理解更深了一層。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容

  • 關于Mongodb的全面總結 MongoDB的內部構造《MongoDB The Definitive Guide》...
    中v中閱讀 32,001評論 2 89
  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網絡請求組件 FMDB本地數據庫組件 SD...
    陽明AGI閱讀 16,003評論 3 119
  • 晨讀感悟《如何改變習慣》斯科特·揚 壞習慣有了就很難戒。但是好習慣卻很難養成。下面就是如何培養好習慣 001習慣的...
    不會飛的艷子閱讀 151評論 0 0
  • 最近狀態不好,失眠,又遇考試,很緊張,考試安排比較緊,四天考四門,大學的最后一次考試,好像莫名的緊張。昨天晚上我一...
    17Q_Q閱讀 176評論 7 4