1. 程式人生 > >Collections中sort()方法原始碼的簡單分析

Collections中sort()方法原始碼的簡單分析

  • Collections的sort方法程式碼:
    public static <T> void sort(List<T> list, Comparator<? super T> c) {
        Object[] a = list.toArray();
        Arrays.sort(a, (Comparator)c);
        ListIterator i = list.listIterator();
        for (int j=0; j<a.length; j++) {
            i.next();
            i.set(a[j]);
        }
    }
這個方法的定義:<T extends Comparable<? super T>> 表示該方法中傳遞的泛型引數必須實現了Comparable中的compareTo(T o)方法,否則進行不了sort排序。

把這個方法細分為3個步驟:

(1)將list裝換成一個物件陣列

(2)將這個物件陣列傳遞給Arrays類的sort方法(也就是說collections的sort其實本質是呼叫了Arrays.sort)

(3)完成排序之後,再一個一個地,把Arrays的元素複製到List中。

  • Collections方法實際是呼叫了Arrays的sort方法實現的,再看Arrays的sort方法程式碼:
    public static void sort(Object[] a, int fromIndex, int toIndex) {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a, fromIndex, toIndex);
        else
            ComparableTimSort.sort(a, fromIndex, toIndex);
    }
注意到1.7下的sort有一個分支判斷,當LegacyMergeSort.userRequested為true的情況下,採用legacyMergeSort,否則採用ComparableTimSort。LegacyMergeSort.userRequested的字面意思大概就是“使用者請求傳統歸併排序”的意思,這個分支呼叫的是與jdk1.5相同的方法來實現功能。

ComparableTimSort是改進後的歸併排序,對歸併排序在已經反向排好序的輸入時表現為O(n^2)的特點做了特別優化。對已經正向排好序的輸入減少回溯。對兩種情況(一會升序,一會降序)的輸入處理比較好(摘自百度百科)。

  • legacyMergeSort程式碼:
    private static void legacyMergeSort(Object[] a,
                                        int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        Object[] aux = copyOfRange(a, fromIndex, toIndex);
        mergeSort(aux, a, fromIndex, toIndex, -fromIndex);
    }
  • mergeSort程式碼:
 private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low,
                                  int high,
                                  int off) {
        int length = high - low;

        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low &&
                         ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }

        // Recursively sort halves of dest into src
        int destLow  = low;
        int destHigh = high;
        low  += off;
        high += off;
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off);
        mergeSort(dest, src, mid, high, -off);

        // If list is already sorted, just copy from src to dest.  This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
            System.arraycopy(src, low, dest, destLow, length);
            return;
        }

        // Merge sorted halves (now in src) into dest
        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }

①:這個方法接收Object[] src,Object[] dest兩個陣列,根據呼叫它的方法可以看出只需要對dest[]這個陣列中的元素進行排序後,傳遞過來的List<T> list也即排好了序,src[]陣列用來進行中介的,也即後面的方法需要呼叫(所以這兩個陣列必須區分作用),這裡有個判斷條件為length < INSERTIONSORT_THRESHOLD,INSERTIONSORT_THRESHOLD為Arrays的一個常量為7,它定義瞭如果陣列元素小於7的話就直接用swap方法排序,提高了程式的執行效率。

②:當陣列元素大於7的時候,程式先將陣列拆分成低區間和高區間兩個區間,再呼叫兩個遞迴對這兩個區間元素進行排序。在遞迴時還得判斷已經劃分的區間元素是否還多於7位,如果多於7位的話繼續劃分成兩個區間,這樣迴圈遞迴呼叫。在這個方法中,要特別注意src[]和dest[]的引數傳遞位置,呼叫遞迴方法時,是將src[]陣列作為排序物件進行排序,src[]排序後,在通過③或④方法將dest[]陣列依據src進行排序。最終達到List<T> list排序的結果。

③:如果初始元素個數大於等於7個的話(小於7的直接在①方法排好序返回)進過②方法後,只有兩種情況:兩個排好序的低區間和高區間。這個方法作用是:如果低區間列表中的最高元素小於高區間列表中的最低元素,則表明該次遞迴迴圈的區間段已經排好序,然後將這段資料複製到dest[]陣列中。反之則進入方法④。

④:進入該方法表明該次遞迴迴圈的低區間的數字最高元素大於高區間列表中的最低元素,也就是說低區間的陣列元素值都大於高區間的陣列元素值,因此將src中的高區間元素和低區間元素調換放入dest陣列中。這樣一次遞迴迴圈就呼叫完畢,如果還有迴圈就繼續排序下去,否則排序就已經完成。

static void sort(Object[] a, int lo, int hi) {
        rangeCheck(a.length, lo, hi);
        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;
        }

        /**
         * 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.
         */
        ComparableTimSort ts = new ComparableTimSort(a);
        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);
                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;
    }
 

您的關注是我堅持寫作的動力,如果覺得有用,歡迎關注我的微信,海量學習資源免費送!

你的關注是對我最大的鼓勵!

參考: