Timsort——Java源码阅读记录

       在工作的时候用到了对泛型集合排序,本来以为里面也是像排序数组一样使用双轴快排,但是跟着Collections.sort()源码里发现是用了TimSort.sort()的排序,去网上简单的搜索了一下,说TimSort用了归并排序,并极大程度的利用了自然界很多数都已经拍好序了这个规律, 其中比较好的文章是:世界上最快的排序算法——Timsort,我在看完源码之后虽然看"懂"了,TimSort它是怎么做的,但是不清楚它为什么这么做,这篇文章就解答了我很多疑惑。为了帮助大家更好的理解TimSort,也为了让我记录一下Timsort的笔记,所以我写了这篇博客来和大家分享一下我对JDK1.8版本源码TimSort的阅读心得。

      我们先跟Collections.sort()进去发现是调用传进来集合的list.sort(),代码如下。可以看到主要干了三件事情,先是调用本身的toArray()方法生成一个数组,再调用Arrays.sort()方法对数组进行排序,最后将排序好的结果再塞回list里

     

default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

        下面是Arrays.sort()的代码,可以看到如果没有传比较器的话就会调用ComparableTimSort.sort(),如果传了比较器则直接使用Tim.sort(), 至于legacyMergeSort(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);
        }
    }

      我们直接跟进去TimSort.sort()方法,ComparableTimSort里的sort方法逻辑是和Timsort一样的,只不过是元素间进行比较的时候有区别而已。

     这个方法是完成整个排序的核心方法,所有的排序都会在这个方法里完成。

     改方法里调用的其它方法我都会下面进行详细讲解。

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
        //  private static final int MIN_MERGE = 32;
        // 如果要排序的部分小于这个最小的阈值,则进行二分插值排序
        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 
        TimSort<T> ts = new TimSort<>(a, c, work, workBase, workLen);
        // 计算出单个run的最小长度,run可以看出是原数组的一个排序段,每个run内部都是升序的
        int minRun = minRunLength(nRemaining);
        do {
            // Identify next run
            // 首先计算出数组刚已经排序好的部分
            int runLen = countRunAndMakeAscending(a, lo, hi, c);

            // If run is short, extend to min(minRun, nRemaining)
            // 如果数组中有序部分的长度无法满足run的最小长度
            if (runLen < minRun) {
                // 取还需要排序的字段和minRun 的最小值
                int force = nRemaining <= minRun ? nRemaining : minRun;
                // 对force部分进行二分插值排序
                binarySort(a, lo, lo + force, lo + runLen, c);
                runLen = force;
            }

            // Push run onto pending-run stack, and maybe merge
            // 至此run里所有的元素都是升序的,将该run放在栈中
            ts.pushRun(lo, runLen);
            // 合并run
            ts.mergeCollapse();

            // Advance to find next run
            // 移动lo,寻找下一个run
            lo += runLen;
            // 更新还需要排序的长度
            nRemaining -= runLen;
        } while (nRemaining != 0);

        // Merge all remaining runs to complete sort
        assert lo == hi;
        // 强制合并剩余的所有run
        ts.mergeForceCollapse();
        assert ts.stackSize == 1;
    }

    countRunAndMakeAscending方法,从这个方法就可以看出TimSort非常相信在一个数组里,有很多段是有序的,无论它是降序还是升序。

   

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

/**
* 一个很简单的左右指针反转数组元素方法
*/
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;
        }
    }

     binarySort方法 

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;
            /*
             * Invariants:
             *   pivot >= all in [lo, left).
             *   pivot <  all in [right, start).
             */
            // 接着在已经排序好的字段区间里通
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值