原文链接:https://blog.csdn.net/u013066244/article/details/78997869

环境
jdk:1.7+

前言
之前我写过关于comparator的理解,但是都理解错了。

java 自定义排序【Comparator升序降序的记法】

特别是 上面这篇,完全理解错了,排序的真正的意思。

最近通过查看源码,打断点的方式,一步步的查看、演算。算是明白了!

当时我心里的疑惑是:
① -1到底表示不表示倒序;
② -1、0、1这三个值真的需要同时使用吗?能不能只使用其中某个就行了。
③-1是不是就是表示不调整顺序,其他都是要调整顺序。

真正正确的理解:
① jdk官方默认是升序,是基于:

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

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

< return 1
= return 0
> return -1
1
2
3
为什么呢?这个只能通过源码的方式去看了。

测试代码
首先,我写了如下的测试代码:

public static void main(String[] args) {
List<Integer> re = new ArrayList<>();

re.add(1);
re.add(2);
re.add(6);
re.add(5);
re.add(8);
re.add(8);
re.add(4);

Collections.sort(re, new Comparator<Integer>() {

@Override
public int compare(Integer o1, Integer o2) {
//下面这么写,结果是降序
if(o1 < o2){
return 1;
}else if(o1 > o2){
return -1;
}
return 0;
}

});

System.out.println(re);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
降序
开始debug测试:

第一步: 程序先调用如下方法:

@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
1
2
3
4
第二步: 而list.sort(c)源码:
这里调用的是ArrayList类的方法:

@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
2
3
4
5
6
7
8
9
10
11
第三步:调用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
2
3
4
5
6
7
8
9
10
11
12
第四步:TimSort.sort(a, 0, a.length, c, null, 0, 0);这个方法很长,我先贴出主要核心的:

if (nRemaining < MIN_MERGE) {
int initRunLen =
//这个方法就大致决定是顺序
countRunAndMakeAscending(a, lo, hi, c);
binarySort(a, lo, hi, lo + initRunLen, c);
return;
}
1
2
3
4
5
6
7
第五步: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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这个方法就是关键;

我上面创建了一个数组:

1 2 6 5 8 8 4
//其中
< 1
= 0
> -1
1
2
3
4
5
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);
1
2
3
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);。这个是个反转方法。
效果就是:

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

第六步:执行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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
例子中的数组:

6 2 1 5 8 8 4
//循环执行binarySort方法后,
//会依次把 5 8 8 4 插入到相应的位置
//最终的结果为:
// 8 8 6 5 4 2 1
1
2
3
4
5
升序
这是,jdk默认的顺序,例子:

< -1 > 1 =0
1 2 6 5 8 8 4
1
2
执行步骤和上面降序是一样的,我就直接分析核心部分了:

// 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++;
}
1
2
3
4
5
6
7
8
9
当执行到这里时,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++;
1
2
代码c.compare(a[runHi], a[runHi - 1])就是c.compare(6, 2)返回的是1符合条件(大于0),
runHi++,此时runHi=3。c.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);`中用到。
1
2
3
4
下一步:执行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
2
3
4
5
6
7
8
9
通过升序和降序,我们基本可以知道排序步骤:
①countRunAndMakeAscending这个方法确定是顺序还是降序,并且将数组的一部分排列好。并返回未排列的起始位置
②将未排列的起始位置传递给binarySort进行二分插入排序。

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

1 2 6 5 8 8 4
倒序后:
4 8 8 5 6 2 1
//怎么做到呢?
//不管大于、小于和等于 都返回 -1
1
2
3
4
5
从源码上看countRunAndMakeAscending方法:

f (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++;
}
1
2
3
4
5
6
7
8
c.compare()得到的永远都是-1,所以其会将下面这段代码执行完毕:

while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
runHi++;
1
2
循环完毕后,此时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++;
}
1
2
3
4
5
6
7
8
9
10
当不管大于、小于、等于时,我们都返回一个值时,0和1效果是一样的,就是不排序;-1就是倒序。

可以要是 是如下写法:

public int compare(Integer o1, Integer o2) {
if(o1 < o2){
return 1;
}/*else if(o1 > o2){
return 1;
}*/
return -1;
}
1
2
3
4
5
6
7
8
也就是 我们把等于和大于都返回-1,小于返回1。发现也是可以降序的,或者反过来,就是升序。视乎觉得0好像是多余的。

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

1 2 6 5 8 8 4
//也就是这里面两个8 会交换顺序
1
2
对数字而言交换顺序没有关系,但是里面要是是Map对象的话,那就有关系,因为有时我们是希望相同元素不进行顺序调整的。

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

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

升序(默认,即官方定义,毕竟代码实现就是基于这个写的):

< -1
= 0 //或者 1效果是一样的;-1相同元素会发生位置调整
> 1
1
2
3
降序:

< 1
= 0 //或者 1效果是一样的;-1相同元素会发生顺序调整
> -1
1
2
3
倒序:

//直接
return -1;
1
2
不改变顺序:

//直接
return 0或者1;
1
2
底层做法是:先确定局部顺序,再利用二分查找法,进行后续排序:

数组:1 2 6 5 8 8 4
反转后:6 2 1 5 8 8 4
1
2
这里先确定了6 2 1的顺序,后面5 8 8 4的位置就是利用二分查找法来确定的!
---------------------
作者:山鬼谣me
来源:CSDN
原文:https://blog.csdn.net/u013066244/article/details/78997869
版权声明:本文为博主原创文章,转载请附上博文链接!

【转】java comparator 升序、降序、倒序从源码角度理解的更多相关文章

  1. 从源码角度理解Java设计模式——装饰者模式

    一.饰器者模式介绍 装饰者模式定义:在不改变原有对象的基础上附加功能,相比生成子类更灵活. 适用场景:动态的给一个对象添加或者撤销功能. 优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个 ...

  2. 【java】实体类中 按照特定的字段 进行升序/降序 排序

    背景: 实际页面上  所有的分值都是按照JSON格式存储在一个字符串中 存储在同一个字段中: {"ownPTotal":"10>0","ownO ...

  3. TreeMap升序|降序排列和按照value进行排序

    TreeMap 升序|降序排列 import java.util.Comparator; import java.util.TreeMap; public class Main { public st ...

  4. js学习篇--数组按升序降序排列

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. C# List.sort排序详解(多权重,升序降序)

    很多人可能喜欢Linq的orderBy排序,可惜U3D里面linq在Ios上会报错,所以就必须使用list的排序. 其实理解了并不难 升序降序比较 sort有三种结果 1,-1,0分别是大,小,相等. ...

  6. C# List.sort排序(多权重,升序降序)

    很多人可能喜欢Linq的orderBy排序,可惜U3D里面linq在Ios上会报错,所以就必须使用list的排序. 其实理解了并不难 升序降序比较 sort有三种结果 1,-1,0分别是大,小,相等. ...

  7. mysql_DML_select_升序降序去重

    select *from wsb   limit 5;显示前5行 select *from students LIMIT  (m,n) (其中m是指记录开始的index,从0开始,表示第一条记录n是指 ...

  8. C++员工管理系统(封装+多态+继承+分类化+函数调用+读写文件+指针+升序降序算法等一系列知识结合)

    1 C++职工管理系统 2 该项目实现 八个 功能 3 1-增加功能 2-显示功能 3-删除功能 4-修改功能 4 5-查找功能 6-排序功能 7-清空功能 8-退出功能 5 实现多个功能使用了多个C ...

  9. [算法1-排序](.NET源码学习)& LINQ & Lambda

    [算法1-排序](.NET源码学习)& LINQ & Lambda 说起排序算法,在日常实际开发中我们基本不在意这些事情,有API不用不是没事找事嘛.但必要的基础还是需要了解掌握. 排 ...

随机推荐

  1. [转帖]2019 简易Web开发指南

    2019 简易Web开发指南     2019年即将到来,各位同学2018年辛苦了. 不管大家2018年过的怎么样,2019年还是要继续加油的! 在此我整理了个人认为在2019仍是或者将成为主流的技术 ...

  2. RANCHER2.0 的简单使用

    1. RANCHER2.0  能够管理 k8s 集群 也能够用来搭建 k8s 集群 但是因为网络问题 只测试了如何去管理集群 还没有去 测试 安装集群. 2. 创建rancher 服务的方法 dock ...

  3. 微信小程序填坑之旅一(接入)

    一.小程序简介 小程序是什么? 首先“程序”这两个字我们不陌生.看看你手机上的各个软件,那就是程序.平时的程序是直接跑在我们原生的操作系统上面的.小程序是间接跑在原生系统上的.因为它嵌入在微信中,受微 ...

  4. python 多参数并行化

    multiprocessing模块与map方法 import time from datetime import datetime from multiprocessing.dummy import ...

  5. NOI前各种Idea总结以及各种文本乱堆

    转载请注明原文地址:https://www.cnblogs.com/LadyLex/p/9227267.html 不过这篇的确没什么*用了转转吧 2018-6-24 关于一类延迟标记(来自UR14 思 ...

  6. Xml文档添加节点和属性

    XmlDocument doc = new XmlDocument(); XmlElement xmlElement = doc.CreateElement("节点名称"); xm ...

  7. MT【188】一个正切余切有关的恒等式

    (2017北大特优)求$9\tan 10^\circ+2\tan 20^\circ+4\tan 40^\circ-\tan 80^\circ=$_____ A.$0$ B.$\dfrac{\sqrt ...

  8. MT【99】2005联赛二试题我的一行解法

    为表示尊敬先展示参考答案:参考答案其实很好的体现了当年出题人陶平生的想法,就是利用已知形式联想到三角里的射影定理,从而写出余弦定理形式,利用三角解题,如下: 这里展示以下几年前做这题时我的解法: $\ ...

  9. 动态生成web表-asp.net table

    1. 页面上定义一个server 的table <table style="width: 100%" id="tbContent" runat=" ...

  10. 如何设置Ultraedit自动换行

    有时候这会非常麻烦, 要让Ultraedit自动换行请按发下方法: 1. 点击菜单栏的"高级→配置",找到"编辑器→自动换行/制表符设置". 2. 然后,把&q ...