美文网首页
源码分析 - Collections.sort()

源码分析 - Collections.sort()

作者: YangEvol | 来源:发表于2019-08-03 19:06 被阅读0次

Collections.sort():是对一个集合进行正向排序的方法
首先,传入Collections.sort()的集合的元素类型要继承Comparator<T>,这样才能保证可以比较并排序。
下面举个例子,我要正序排列100,50,70,60这四个数字,代码如下:

        List<Integer> result = new ArrayList<Integer>(){{add(100);add(50);add(70);add(60);}};
        Collections.sort(result);
        for (Integer integer : result) {
            System.out.println(integer);
        }
    }

进到 Collections.sort()方法中:

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

因为没有传入Comparable对象,所以 list.sort(null);中为null
接着往下走:

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

可以看到,在 list.sort()方法中,将传进来的List集合转换成了一个Object数组,
接着调用Arrays.sort()方法,进到里面看一下,

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

这里有一个分支:
LegacyMergeSort.userRequested:百度一下-用户请求传统归并排序,这个方法不做讨论,除非用户特殊要求,一般会走另一个分支:TimSort.sort()
这个就是整个排序的方法了:

     * @param a the array to be sorted 排序的数字
     * @param lo the index of the first element, inclusive, to be sorted  要排序的第一个元素(包括)的索引
     * @param hi the index of the last element, exclusive, to be sorted 要排序的最后一个元素(不含)的索引
     * @param c the comparator to use 参数c要使用的比较器
     * @param work a workspace array (slice) 参数工作区数组(切片)
     * @param workBase origin of usable space in work array 工作数组中可用空间的参数WorkBase原点
     * @param workLen usable size of work array 工作阵列的参数工作状态可用大小
     *  MIN_MERGE  = 32;
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;
        
        // 如果数组中只有0或者1个元素,则不需要排序了
        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
        // 如果数组很小(小于32个元素),调用不包含合并操作的mini-TimSort
        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);
        int minRun = minRunLength(nRemaining);
        do {
            // Identify next run
            ////跟二叉查找插入排序一样,先找自然升序序列
            int runLen = countRunAndMakeAscending(a, lo, hi, c);

            // If run is short, extend to min(minRun, nRemaining)
            //  // 如果 自然升序的长度不够minRun,就把 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
             //把指针后移runLen距离,准备开始下一轮片段的排序
            lo += runLen;
            //剩下待排序的数量相应的减少 runLen
            nRemaining -= runLen;
        } while (nRemaining != 0);

        // Merge all remaining runs to complete sort
        assert lo == hi;
        ts.mergeForceCollapse();
        assert ts.stackSize == 1;
    }

解释一下用到的第一个方法:

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

解释一下用到的第二个方法:
Binary Sort:使用二分查找的方法将后续的数插入之前的已排序数组,binarySort 对数组 a[lo:hi] 进行排序,并且a[lo:start] 是已经排好序的。算法的思路是对a[start:hi] 中的元素,每次使用binarySearch 为它在a[lo:start] 中找到相应位置,并插入。

 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
            //控制要排序元素的区间,最左是lo,最右是start
            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 (c.compare(pivot, a[mid]) < 0)
               //如果pivot比中间数的值小,最右的下标变成中间数的下标,相当于去掉数组的右半部分
                    right = mid;
                else
                //最左的下标变成中间数的下标,相当于去掉数组的左半部分
                    left = mid + 1;
            }
          //直到最右下标相等,找到 pivot可以插入的位置
            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.
             */
          // 插入point
          // 需要移动的范围的长度
            int n = start - left;  // The number of elements to move
            // Switch is just an optimization for arraycopy in default case
            //1-2个元素的移动就不需要System.arraycopy了
            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;
        }
    }

解释一下用到的第三个方法:

    private static int minRunLength(int n) {
        assert n >= 0;
        int r = 0;      // Becomes 1 if any 1 bits are shifted off
        while (n >= MIN_MERGE) {
            r |= (n & 1);
            n >>= 1;
        }
        return n + r;
    }

a) 如果数组大小为2的N次幂,则返回16(MIN_MERGE / 2)
b) 其他情况下,逐位向右位移(即除以2),直到找到介于16和32间的一个数。这个函数根据 n 计算出对应的 natural run 的最小长度。MIN_MERGE 默认为32,如果n小于此值,那么返回n 本身。否则会将 n 不断地右移,直到少于 MIN_MERGE,同时记录一个 r 值,r 代表最后一次移位n时,n最低位是0还是1。 最后返回 n + r,这也意味着只保留最高的 5 位,再加上第六位

相关文章

网友评论

      本文标题:源码分析 - Collections.sort()

      本文链接:https://www.haomeiwen.com/subject/foojdctx.html