原文链接: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. 组件 -- Button

    .btn --------------------------------- button的背景色: .btn-primary .btn-success .btn-secondary .btn-dan ...

  2. vmEsxi一些使用

    打开esxi的shell:在故障检查选项中 回车,打开shell ALT+F1进入esxi的shell ALT+F2返回 精简置备--用多少占多少,上限为设置的磁盘大小 虚机扩容:1.原本的扩容2.添 ...

  3. 【Vue学习笔记1】全局配置 Vue.config

    1.slient 类型:boolean: 默认:false: 用法:Vue.config.silent = true  用于取消 Vue 所有的日志与警告

  4. java学习之switch 等值判断

    当匹配到相等的值时候 则进入case里面执行语句 当该语句有break时候 则退出匹配 当没有break时候 则继续往下匹配 直到遇到break才停止匹配

  5. sql bak还原到新数据库

    1 创建新数据库  TestDB 2  使用语句 use master restore database [TestDB] from disk = 'D:\SqlDataBak\SanJu\SanJu ...

  6. Spring Shell介绍

    最近开发中在下遇到了spring-shell开发工具的项目,现在整理了相关文章,以供大家学习 本博客相关的文章均是在Spring Shell 1.2.0的基础上建立   Spring Shell介绍 ...

  7. 利用EF和C#泛型实现通用分页查询

    利用EF和C#泛型实现通用分页查询       Entity Framework 是微软以 ADO.NET 为基础所发展出来的对象关系对应 (ORM) 解决方案,是微软的ORM框架.此框架将数据库中的 ...

  8. POJ 2387 Til the Cows Come Home (图论,最短路径)

    POJ 2387 Til the Cows Come Home (图论,最短路径) Description Bessie is out in the field and wants to get ba ...

  9. 关于 Java 中的 Null

    什么是Java中的Null? null在Java中是一个非常重要的概念,它最初是为了表示缺少某些东西,例如缺少用户.资源或任何东西而发明出来的.但是这也为Java程序员带来了很多麻烦,比如最常见的空指 ...

  10. lrzsz 移植到 ARM-linux 嵌入式板子上

    特别说明:SSH 或 串口 都可以使用 lrzsz 进行通信 lrzsz是一个Unix通信包,提供XMODEM.YMODEM和ZMODEM文件传输协议.lrzsz以前是Omen科技的主打软件,现在已经 ...