Arrays.sort和Collections.sort实现原理解析

5,313 阅读5分钟

Collections.sort方法底层就是调用的Arrays.sort方法。

写一个例子看源码:

public static void main(String[] args) {
        List<String> strings = Arrays.asList("6", "1", "3", "1","2");
 
        Collections.sort(strings);//sort方法在这里
 
        for (String string : strings) {
 
            System.out.println(string);
        }
}

跟踪代码,发现Collections.sort调用的是list.sort方法:

   @SuppressWarnings("unchecked")
    public static <T extends Comparable<? super T>> void sort(List<T> list) {
        list.sort(null);
    }

发现调用的是Arrays.sort(a,c):

    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);    //我的断点走的是这一方法
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);    //看其他帖子,有走这个的,下面在我后面贴出其他帖子的步骤,
        }
    }

-------------------------我实际走的断点在下面展示------------start-------------

点进去,sort(a);

public static void sort(Object[] a) {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a);
        else
            ComparableTimSort.sort(a, 0, a.length, null, 0, 0); //然后断点是走这一步
    }

点进去,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;
        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;
        }

点进去,binarySort(a, lo, hi, lo + initRunLen);最终在这里,完成排序

@SuppressWarnings({"fallthrough", "rawtypes", "unchecked"})
    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;
            /*
             * Invariants:
             *   pivot >= all in [lo, left).
             *   pivot <  all in [right, start).
             */
            while (left < right) {
                int mid = (left + right) >>> 1;
                if (pivot.compareTo(a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
            }
            assert left == right;

            /**
             * The invariants still hold: pivot >= all in [lo, left) and
             * pivot < all in [left, start), so pivot belongs at left.  Note
             * that if there are elements equal to pivot, left points to the
             * first slot after them -- that's why this sort is stable.
             * Slide elements over to make room for pivot.
             */
            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;
        }
    }

综上所述:我的断点,最后走的是ComparableTimSort(比较时间排序)类中的binarySort(二元排序)

-------------------------我实际走的断点在上面面展示------------end-------------

-------------------------下面是其他帖子的描述------------start-------------

发现调用的是Arrays.sort(a,c):

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

没有写比较器c,跟踪LegacyMergeSort,结果如下:

/**
 * Old merge sort implementation can be selected (for
 * compatibility with broken comparators) using a system property.
 * Cannot be a static boolean in the enclosing class due to
 * circular dependencies. To be removed in a future release.
 */
static final class LegacyMergeSort {
    private static final boolean userRequested =
        java.security.AccessController.doPrivileged(
            new sun.security.action.GetBooleanAction(
                "java.util.Arrays.useLegacyMergeSort")).booleanValue();
}

是一种老的归并排序,我们走的是下面TimSort.sort()这个方法:

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;
    }

    /**
     * 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.
     */
    TimSort<T> ts = new TimSort<>(a, c, work, workBase, workLen);
    int minRun = minRunLength(nRemaining);
    do {
        // Identify next run
        int runLen = countRunAndMakeAscending(a, lo, hi, c);

        // 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, c);
            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;
}

-------------------------上面是其他帖子的描述------------end-------------

不论是Collections.sort方法或者是Arrays.sort方法,底层实现都是TimSort实现的,这是jdk1.7新增的,以前是归并排序。TimSort算法就是找到已经排好序数据的子序列,然后对剩余部分排序,然后合并起来。

Colletions.sort(list) 与 Arrays.sort(T[])

Colletions.sort()实际会将list转为数组,然后调用Arrays.sort(),排完了再转回List。 PS. JDK8里,List有自己的sort()方法了,像ArrayList就直接用自己内部的数组来排,而LinkedList, CopyOnWriteArrayList还是要复制出一份数组。

而Arrays.sort(),对原始类型(int[],double[],char[],byte[]),JDK6里用的是快速排序,对于对象类型(Object[]),JDK6则使用归并排序。为什么要用不同的算法呢?

JDK7的进步

到了JDK7,快速排序升级为双基准快排(双基准快排vs三路快排);归并排序升级为归并排序的改进版TimSort,一个JDK的自我进化。

JDK8的进步

再到了JDK8, 对大集合增加了Arrays.parallelSort()函数,使用fork-Join框架,充分利用多核,对大的集合进行切分然后再归并排序,而在小的连续片段里,依然使用TimSort与DualPivotQuickSort。

TimSort:

Timsort是一种结合了归并排序和插入排序的混合算法,由Tim Peters在2002年提出,并且已经成为Python 2.3版本以后内置排序算法,并且Java SE 7, Android平台,GNU Octave也引入了这一排序算法。简单来说,这个算法可以概括为两步: 1) 第一步就是把待排数组划分成一个个run,当然run不能太短,如果长度小于minrun这个阈值,则用插入排序进行扩充; 2) 第二步将run入栈,当栈顶的run的长度满足:runLen[n-2] <= runLen[n-1] + runLen[n]或者 runLen[n-1] <= runLen[n], 则对两个短run归并为一个新run,则到只剩栈顶元素时排序也完成了。

PS:暂时先记录下,后面有时间,在仔细分析个中排序算法