文章目录

  • 写法
  • 测试代码
  • 降序源码分析
  • 升序源码分析
  • 倒序源码分析
  • 0 到底是什么作用
  • 总结


写法

升序标准写法(官方写法),jdk官方默认是升序:

< return -1 //意思就是
= return 0
> return 1
官方的源码就是基于这个写的;可以理解为硬性规定。也就是说,排序是由这三个参数同时决定的。

如果要降序就必须完全相反:

<  return 1 
= return 0 
> return -1
测试代码
public class TestCompare {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(6);
        list.add(5);
        list.add(8);
        list.add(8);
        list.add(4);

        Collections.sort(list, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                //下面这么写,结果是升序
                /*如果保持这个顺序就返回-1,交换顺序就返回1,什么都不做就返回0;所以 升序的话 如果o1<o2,就返回-1。*/
                //if (o1 < o2 ) {
                //     return  -1;
                //}else if (o1 > o2) {
                //    return 1;
                //}
                //return  0;

                //下面这么写,结果是降序
                /*如果保持这个顺序就返回-1,交换顺序就返回1,什么都不做就返回0;所以 升序的话 如果o1<o2,就返回-1。*/
               if (o1 < o2) {
                    return 1;
                } else if (o1 > o2) {
                    return -1;
                }
                return 0;
            }
        });

        System.out.println(list);
    }
}

测试结果:升序

java接口实现异步返回_java接口实现异步返回


测试结果:降序

java接口实现异步返回_java接口实现异步返回_02

为什么呢?这个只能通过源码的方式去看了

降序源码分析
  1. 第一步进入源码Collections类中找到sort方法
@SuppressWarnings({"unchecked", "rawtypes"})
    public static <T> void sort(List<T> list, Comparator<? super T> c) {
        list.sort(c);
    }
  1. 进入上面list.sort(c)源码:
@SuppressWarnings({"unchecked", "rawtypes"})
    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);
        }
    }
  1. 进入上面Arrays.sort(a, (Comparator) 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
           //接下来会走这个方法,上面不会走;
          //未来jdk会弃用legacyMergeSort方法。
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }
  1. 进入上面的 TimSort.sort(a, 0, a.length, c, null, 0, 0);这个方法很长,我先贴出主要核心的:
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;
    }
  1. 进入上面的 countRunAndMakeAscending方法
private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi, Comparator<? super T> c) {
        // lo 是数组起始位置 也就是 0 
        assert lo < hi;
        // runHi = 1,这个值会随着循环而改变,表示当前元素的位置
        int runHi = lo + 1;
        // hi是数组长度
        if (runHi == hi)
            return 1;

        // Find end of run, and reverse range if descending
        //这里c.compare()调用就是我们重写的方法
        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;
    }

这个方法就是关键;

1 2 6 5 8 8 4
其中
if (o1 < o2) {// 01 小于 02 交换位置
return 1;
} else if (o1 > o2) {// 不交换位置
return -1;
}
return 0;//什么都不做

if (c.compare(a[runHi++], a[lo]) < 0)— 这句代码,对我的测试代码而言:if (c.compare(2, 1) < 0)c.compare(2,1)得到的就是-1。接着就是执行:

while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
   runHi++;
   reverseRange(a, lo, runHi);

while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)c.compare(a[runHi], a[runHi - 1]) < 0)就是 c.compare(6, 2) < 0),而 c.compare(6, 2)返回的是-1,所以会接着循环执行,runHi++后,此时runHi=2。就我的测试代码就会去判断c.compare(5, 6),其返回的是1,循环结束,接着执行reverseRange(a, lo, runHi);。这是个反转方法

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

效果就是:

数组:1 2 6 5 8 8 4
反转后:6 2 1 5 8 8 4

可以看出,前面三个数字顺序已经好了,后面的5 8 8 4,会在执行binarySort(a, lo, hi, lo + initRunLen, c);这个方法时来进行二分插入排序

  1. 执行binarySort(a, lo, hi, lo + initRunLen, c)方法:
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).
             */
            //这个是关键地方
            while (left < right) {
            //这里相当于除以2
                int mid = (left + right) >>> 1;
                if (c.compare(pivot, a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
            }
        //当left等于right时,就说明找到位置了。
        //assert是断言,要是为false会直接报错
            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;
          //要是移动的位数大于2,就执行如下方法;
                default: System.arraycopy(a, left, a, left + 1, n);
            }
            a[left] = pivot;
        }
    }

例子中的数组:

6 2 1 5 8 8 4
//循环执行binarySort方法后,
//会依次把 5 8 8 4 插入到相应的位置
//最终的结果为:
8 8 6 5 4 2 1

升序源码分析

升序jdk默认的顺序,例子:

< -1  
> 1 
> =0

//1 2 6 5 8 8 4

执行步骤1,2,3,4步骤和降序是一样的,直接分析核心部分countRunAndMakeAscending:

private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi, Comparator<? super T> c) {
        // lo 是数组起始位置 也就是 0 
        assert lo < hi;
        // runHi = 1,这个值会随着循环而改变,表示当前元素的位置
        int runHi = lo + 1;
        // hi是数组长度
        if (runHi == hi)
            return 1;

        // Find end of run, and reverse range if descending
        //这里c.compare()调用就是我们重写的方法
        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;
    }

当执行到这里时,c.compare(a[runHi++], a[lo]) < 0就是 c.compare(2, 1) < 0,而c.compare(2, 1)返回的是1,那么程序就会进入else的部分

while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
     runHi++;

代码 c.compare(a[runHi], a[runHi - 1])就是 c.compare(6, 2)返回的是 1符合条件(大于0),
runHi++,此时 runHi=3c.compare(a[runHi], a[runHi - 1])就是 c.compare(5, 6),其返回的是 -1,不符合条件。循环结束,数组结果为:

//可以看出什么都没有变
1 2 6 5 8 8 4
//但是方法的return runHi - lo;这个返回的结果就是3
//这个返回值,会在binarySort(a, lo, hi, lo + initRunLen, c);中用到。

下一步:执行binarySort(a, lo, hi, lo + initRunLen, c);其中 initRunLen = 3;在执行二分插入时,就会从数组下标为3开始;

1 2 6 5 8 8 4
//从下标为3,开始二分插入排序;即从5开始。
1 2 5 6 8 8 4
接着是8
1 2 5 6 8 8 4
接着是第二个8
1 2 5 6 8 8 4
接着是4
1 2 4 5 6 8 8

通过升序和降序,我们基本可以知道排序步骤:

  1. countRunAndMakeAscending这个方法确定是顺序还是降序,并且将数组的一部分排列好。并返回未排列的起始位置
  2. 将未排列的起始位置传递给binarySort进行二分插入排序
倒序源码分析

我们先来看看倒序的结果:

1 2 6 5 8 8 4
倒序后:
4 8 8 5 6 2 1
//怎么做到呢?
//不管大于、小于和等于 都返回 -1

从源码上看countRunAndMakeAscending方法:

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

c.compare()得到的永远都是-1,所以其会将下面这段代码执行完毕:

while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
                runHi++;

循环完毕后,此时runHi就是数组的长度7
接着执行reverseRange(a, lo, runHi);,将整个数组进行倒序。
该方法完全执行完成后,返回值就是数组长度。
此时再执行binarySort方法时,for ( ; start < hi; start++)中的start是刚刚传进来的值,也就是数组长度,而hi也是数组长度,所以二分插入方法什么都没有做,只是调用了下

0 到底是什么作用

假设不管大于、小于、等于,我们都返回0,会发现顺序没有变;而且你会发现,要是都返回1的话,顺序也是没有变的!
countRunAndMakeAscending方法中可以得出结论:

// 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 {
        //走这个循环                              
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
                runHi++;
        }

当不管大于、小于、等于时,我们都返回一个值时,0和1效果是一样的,就是不排序;-1就是倒序。

可以要是 是如下写法:

public int compare(Integer o1, Integer o2) {
                if(o1 < o2){
                    return 1;
                }/*else if(o1 > o2){
                    return 1;
                }*/
                return -1;
            }

也就是 我们把等于和大于都返回-1,小于返回1。发现也是可以降序的,或者反过来,就是升序。视乎觉得0好像是多余的。

其实0表示的是,相同元素不排序,要是我们把等于返回为-1,那么两个相同的元素会交换顺序;

1 2 6 5 8 8 4
//也就是这里面两个8 会交换顺序

对数字而言交换顺序没有关系,但是里面要是是Map对象的话,那就有关系,因为有时我们是希望相同元素不进行顺序调整的。

要是我们把等于返回为1效果和0是一样的都是不排序。

总结

排序其实是由三个数字同时决定的;

  • 保持这个顺序就返回-1,
  • 交换顺序就返回1
  • 什么都不做就返回0;所以 升序的话 如果1<2,返回-1,保持顺序[1,2],如果3>2,返回-1,交换顺序[2,3]
  1. 升序(默认,即官方定义,毕竟代码实现就是基于这个写的):
< -1
= 0 //或者 1效果是一样的;-1相同元素会发生位置调整
> 1
  1. 降序
< 1
= 0 //或者 1效果是一样的;-1相同元素会发生顺序调整
> -1
  1. 倒序
//直接 
return -1;
  1. 不改变顺序
//直接
return 0或者1;

底层做法是:先确定局部顺序,再利用二分查找法,进行后续排序

数组:1 2 6 5 8 8 4
反转后:6 2 1 5 8 8 4

这里先确定了6 2 1的顺序,后面5 8 8 4的位置就是利用二分查找法来确定的!