【转】java comparator 升序、降序、倒序从源码角度理解
原文链接: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 升序、降序、倒序从源码角度理解的更多相关文章
- 从源码角度理解Java设计模式——装饰者模式
一.饰器者模式介绍 装饰者模式定义:在不改变原有对象的基础上附加功能,相比生成子类更灵活. 适用场景:动态的给一个对象添加或者撤销功能. 优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个 ...
- 【java】实体类中 按照特定的字段 进行升序/降序 排序
背景: 实际页面上 所有的分值都是按照JSON格式存储在一个字符串中 存储在同一个字段中: {"ownPTotal":"10>0","ownO ...
- TreeMap升序|降序排列和按照value进行排序
TreeMap 升序|降序排列 import java.util.Comparator; import java.util.TreeMap; public class Main { public st ...
- js学习篇--数组按升序降序排列
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- C# List.sort排序详解(多权重,升序降序)
很多人可能喜欢Linq的orderBy排序,可惜U3D里面linq在Ios上会报错,所以就必须使用list的排序. 其实理解了并不难 升序降序比较 sort有三种结果 1,-1,0分别是大,小,相等. ...
- C# List.sort排序(多权重,升序降序)
很多人可能喜欢Linq的orderBy排序,可惜U3D里面linq在Ios上会报错,所以就必须使用list的排序. 其实理解了并不难 升序降序比较 sort有三种结果 1,-1,0分别是大,小,相等. ...
- mysql_DML_select_升序降序去重
select *from wsb limit 5;显示前5行 select *from students LIMIT (m,n) (其中m是指记录开始的index,从0开始,表示第一条记录n是指 ...
- C++员工管理系统(封装+多态+继承+分类化+函数调用+读写文件+指针+升序降序算法等一系列知识结合)
1 C++职工管理系统 2 该项目实现 八个 功能 3 1-增加功能 2-显示功能 3-删除功能 4-修改功能 4 5-查找功能 6-排序功能 7-清空功能 8-退出功能 5 实现多个功能使用了多个C ...
- [算法1-排序](.NET源码学习)& LINQ & Lambda
[算法1-排序](.NET源码学习)& LINQ & Lambda 说起排序算法,在日常实际开发中我们基本不在意这些事情,有API不用不是没事找事嘛.但必要的基础还是需要了解掌握. 排 ...
随机推荐
- kafka 数据一致性-leader,follower机制与zookeeper的区别;
我写了另一篇zookeeper选举机制的,可以参考:zookeeper 负载均衡 核心机制 包含ZAB协议(滴滴,阿里面试) 一.zookeeper 与kafka保持数据一致性的不同点: (1)zoo ...
- 05.基于IDEA+Spring+Maven搭建测试项目--web.xml配置
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" " ...
- fgt2eth Script
fgt2eth Script explanation_on_how_to_packet_capture_for_only_certain_TCP_flags_v2.txt Packet capture ...
- wamp安装失败原因大全
wamp 是 Windos.Apache.Mysql.PHP集成安装环境 为了安装hdwiki 所以需要这个环境 1.下载wampserver_x86_3.0.6 64位 环境包,安装路径禁止有空格 ...
- MT【92】空间余弦定理解题
评:学校常规课堂教学里很少讲到这个,有点可惜.
- 【题解】 bzoj3105: [cqoi2013]新Nim游戏 (线性基+贪心)
bzoj3105,懒得复制 Solution: 首先你要有一个前置技能:如果每堆石子异或和为\(0\),则先手比输 这题我们怎么做呢,因为我们没人要先取掉几堆,为了赢对方一定会使剩下的异或和为\(0\ ...
- 基于Docker持续交付平台建设的实践
导读:中国五矿和阿里巴巴联手打造的钢铁服务专业平台五阿哥,通过集结阿里巴巴在大数据.电商平台和互联网产品技术上的优势,为终端用户带来一站式采购体验.本文是五阿哥运维技术团队针对Docker容器技术在如 ...
- springboot中定时任务
import java.text.SimpleDateFormat; import java.util.Date; import org.springframework.context.annotat ...
- 主机 & 虚拟机 & 开发板 相互通信
@2018年7月10日 成功方法之一: 虚拟机设置为桥接模式,保证三者在同一网段,ping方式测试网络连通性OK
- 解题:SDOI 2014 重建
题面 做这个这个题需要稍微深入理解一点矩阵树定理:套矩阵树定理得到的东西是有意义的,它是“所有生成树边权乘积之和”(因为度数矩阵是点的边权和,邻接矩阵是边权),即$\sum_{t}\prod_{e∈t ...