排序是Java中十分基础的功能。尽管平时经常用到,但都没关注具体怎么实现。本文以最常用的集合排序为切入点,分析下Java中集合排序、数组排序的用法,以及引用类型和基本类型排序的实现细节。梳理一遍才发现,底层排序算法比想象中要复杂的多。真应了《道德经》中说的,学之愈多,愈觉知之不足。
集合排序
Java中集合可以通过 java.util.Collections.sort
方法实现排序。
按sort
方法形参不同,可将排序分为两类
基于Comparable
要求实现 java.lang.Comparable
接口
源码
Collections.sort()
中只需要传入list
用法
String, Integer等类已经实现了Comparable
接口,可以直接调用
1 | Collections.sort(list); |
自定义的类需要先实现Comparable
接口,在compareTo
方法中定义排序规则
1 | public class DeviceWeight implements Comparable<DeviceWeight>{ |
基于Comparator
不要求实现java.lang.Comparable
接口,但要提供一个java.util.Comparator
的实现子类作为参数
源码
sort()
需要传入list和Comparator
实例
用法
要求按年龄排序
匿名内部类写法,实现
Comparator
接口,并重写compare
方法1
2
3
4
5
6
7Collections.sort(list, new Comparator<People>() {
// 实现Comparator接口,重写compare方法,按年龄升序排序
public int compare(People o1, People o2) {
return o1.getAge() - o2.getAge();
}
});lambda表达式写法
1
Collections.sort(list, (o1, o2) -> o1.getAge() - o2.getAge());
JDK1.8中写法
1
list.sort(Comparator.comparingInt(People::getAge));
其中
Comparator.comparingInt()
是1.8中的新方法
内部实现
在JDK1.8中,这两个 Collections.sort
方法底层都是调用的都是 java.util.List.sort
方法
List.sort
方法内部又调用了 java.util.Arrays.sort()
方法
可以看到,集合的排序是通过泛型数组(T[])的排序实现的。
数组排序
java.util.Arrays
方法内包含多个sort
方法,按范围可分为部分元素排序和全部元素排序,按数组类型又可以分为引用类型和基本类型(二者之间的排序逻辑不同)。
引用类型排序
引用类型又分为Object和泛型,分别对应是否指定Comparator<? super T> c
的情况
Object数组
不指定Comparator实现子类则会进入此方法。
Arrays.sort(Object[] a)
此方法有两种排序,JDK1.8中默认LegacyMergeSort.userRequested = false
,走ComparableTimSort.sort
如果要走老的归并排序,可以手动设置useLegacyMergeSort
为true
1 | System.setProperty("java.util.Arrays.useLegacyMergeSort", "true"); |
ComparableTimSort.sort
进入ComparableTimSort.sort
。当数组长度<32,进行二分排序。当数组长度>=32,进行Tim排序(基于归并排序和插入排序的混合排序)
重点关注下countRunAndMakeAscending
和binarySort
方法。二者都有调用了数组元素的compareTo
方法,进行比较。如果对象未实现Comparable
接口,那一定会报类型转换异常(java.lang.ClassCastException
)
ComparableTimSort.countRunAndMakeAscending
ComparableTimSort.binarySort
泛型数组
指定Comparator会进入java.util.TimSort.sort
方法
TimSort.sort
进入TimSort.sort
,同样先判断数组长度,当数组长度<32,进行二分排序。当数组长度>=32,进行Tim排序(基于归并排序和插入排序的混合排序)
该方法同样调用了countRunAndMakeAscending
和binarySort
方法,二者都调用了c.compare
用来比较
TimSort.countRunAndMakeAscending
TimSort.binarySort
引用类型排序总结
数组长度 | 所使用的排序算法 |
---|---|
length < 32 | 二分排序 |
length >= 32 | Tim排序 |
基础类型排序
Arrays
中所有基础类型的排序,都会调用java.util.DualPivotQuicksort.sort()
及其重载方法
以int[]
为形参的sort
方法为例,内部调用了以int[]
为形参的DualPivotQuicksort.sort
方法
Arrays.sort(int[] a)
DualPivotQuicksort.sort(int[], int, int, int[], int, int)
其内部排序规则如下:
当数组长度>=286,判断是否基本有序。基本有序则使用快排,基本无序则使用归并排序
当数组长度<286,进入快速排序
DualPivotQuicksort.sort(int[], int, int, boolean)
其中,如果数组长度<=47,直接使用插入排序
基础类型排序总结
数组长度 | 所使用的排序算法 |
---|---|
length < 47 | 插入排序 |
47 <= length < 286 | 快速排序 |
length >= 286 且数组基本有序 | 归并排序 |
length >= 286 且数组基本无序 | 快速排序 |