Collections.sort() / Arrays.sort()內部排序原理(附源碼追蹤)

一、底層使用TimSort,TimSort是什麼呢?

TimSort的核心思想是:先找出序列本身從0索引開始已排好的(遞增)序列(如果是遞減序列則將之翻轉爲遞增),再把剩下的元素進行排序;若序列很長,則把序列分段,重複上述操作,最後把若干段序列合併。

二、源碼詳解(留意註釋)

我們直奔重點,看sort方法(追蹤到ComparableTimSort.class中)

    public static void sort(Object[] a) {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a);
        else
        	//底層調用此方法,a指要排序的數組(集合)
            ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
    }
    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;
        // 數組長度小於2的,不用排
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted

        // 數組長度小於MIN_MERGE(32)的,使用"mini-TimSort"排序
        if (nRemaining < MIN_MERGE) {
        	// 此方法是把數組中從索引0開始的最長的遞減序列找出,並將之按遞增重排;若從索引0開始的是遞增序列,則找出數組中最前的需要重排的索引。(即此方法執行完成後initRunLen索引前的元素順序已排好了。詳解可看“分1”)
            int initRunLen = countRunAndMakeAscending(a, lo, hi);
            // 從initRunLen索引開始,兩兩對比,把大的排後面
            binarySort(a, lo, hi, lo + initRunLen);
            return;
        }

        /**
         * March over the array once, left to right, finding natural runs,
         * extending short natural runs to minRun elements, and merging runs
         * to maintain stack invariant.
         */
        // 數組長度大於MIN_MERGE(32)
        // 算法思想:分總。把序列分成若干段,每段處理:先找出已排好的,再使用mini-TimSort排剩下的。
        // 最後再把若干段合併
        ComparableTimSort ts = new ComparableTimSort(a, work, workBase, workLen);
        int minRun = minRunLength(nRemaining);
        do {
            // Identify next run
            int runLen = countRunAndMakeAscending(a, lo, hi);

            // If run is short, extend to min(minRun, nRemaining)
            if (runLen < minRun) {
                int force = nRemaining <= minRun ? nRemaining : minRun;
                binarySort(a, lo, lo + force, lo + runLen);
                //至此,排好了0-force
                runLen = force;
            }

            // Push run onto pending-run stack, and maybe merge
            ts.pushRun(lo, runLen);
            ts.mergeCollapse();

            // Advance to find next run
            // 爲下一輪排序做準備
            lo += runLen;
            nRemaining -= runLen;
        } while (nRemaining != 0);

        // Merge all remaining runs to complete sort
        // 合併前幾輪排序
        assert lo == hi;
        ts.mergeForceCollapse();
        assert ts.stackSize == 1;
    }

分1:

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

        // Find end of run, and reverse range if descending
        // 索引1和索引0的元素的對比
        if (((Comparable) a[runHi++]).compareTo(a[lo]) < 0) { // Descending 如果是遞減序列
        	//索引2和索引1的元素的對比...以此類推
            while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) < 0)
                runHi++;
          	// 已找到最長遞減序列,將之排好序(詳情看“分2”)
            reverseRange(a, lo, runHi);
        } else {                              // Ascending 如果是遞增序列
            while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) >= 0)
                runHi++;
            // 找出第一個非遞增序列的元素的索引
        }

        return runHi - lo;
    }

分2:(把遞減序列重排爲遞增序列)

    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;
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章