Arrays.sort 和 Collections.sort 实现原理
Arrays.sort
基础知识
DualPivotQuicksort
那么Dual-Pivot快排到底是怎么样的一个排序算法呢?其实从它的名字里面可以看出一些端倪:在经典快排里面有一个pivot的概念,它是用来分隔大数和小数的 -- 这个pivot把一个数组分成两份。那么所谓的Dual-Pivot其实就是用两个Pivot, 把整个数组分成三份。
timeSort
1.扫描数组,确定其中的单调上升段和严格单调下降段,将严格下降段反转。我们将这样的段称之为run。
2.定义最小run长度,短于此的run通过插入排序合并为长度高于最小run长度;
3.反复归并一些相邻run,过程中需要避免归并长度相差很大的run,直至整个排序完成;
4.如何避免归并长度相差很大run呢, 依次将run压入栈中,若栈顶run X,run Y,run Z 的长度违反了X>Y+Z 或 Y>Z 则Y run与较小长度的run合并,并再次放入栈中。 依据这个法则,能够尽量使得大小相同的run合并,以提高性能。注意Timsort是稳定排序故只有相邻的run才能归并。
1.基本类型
public static void sort(char[] a) {
DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
public static void sort(int[] a) {
DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
........
我们可以看到Arrays.sort是一个多态的方法,每一个基本类型都有该方法,并且其都调用的是优化后的快速排序。
下面我们以int[]型为例
DualPivotQuicksort是JDK1.7开始的采用的快速排序算法。
一般的快速排序采用一个枢轴来把一个数组划分成两半,然后递归之。
大量经验数据表面,采用两个枢轴来划分成3份的算法更高效,这就是DualPivotQuicksort。
DualPivotQuicksort,主要做了以下几个方面的优化:
- 当待排序的数组中的元素个数较少时,源码中的阀值为47,采用的是插入排序。尽管插入排序的时间复杂度为0(2),但是当数组元素较少时,插入排序优于快速排序,因为这时快速排序的递归操作影响性能。
- 当待排序的数组个数小于286时,直接使用双枢轴快速排序。
(pivot的选取方式是将数组分成近视等长的七段,而这七段其实是被5个元素分开的,会查看5个元素查看是否有相同,如果有相同。代表重复比较多。会使用单枢轴快速排序)
注意:
如果不相同,将5个元素从小到大排序,取出第2个和第4个,分别作为pivot1和pivot2。
这是个改进的单枢轴快速排序。这个时候也是3个指针的算法。因为,这个算法就像是分成3类,一类小于枢轴,一类大于枢轴,一类等于枢轴。只用对左右两种进行递归 - 大于286时,先使用TimSort的思想,找出正序或者倒序的数(run的栈),然后合并各个run,最后完成TimSort。、
2.对象类型
文档写的是:
可以使用系统属性选择旧的归并排序实现(为了与损坏的比较器兼容)。由于循环依赖关系,在封闭类中不能是静态布尔值。将在未来的版本中删除。
public static void sort(Object[] a) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a);
else
ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}
1.7之前
来看看 mergeSort 的具体实现。
- 当 array.length < 7 时会使用插入排序(insertion sort)
- 其他使用二路归并。
1.7之后
在jdk1.7之后。Arrays类中的sort方法有一个分支推断,当LegacyMergeSort.userRequested为true的情况下,採用legacyMergeSort,否则採用ComparableTimSort。而且在legacyMergeSort的凝视上标明了该方法会在以后的jdk版本号中废弃,因此以后Arrays类中的sort方法将採用ComparableTimSort类中的sort方法。
- 数组大小<32情况下,采用“mini-TimeSort”,实质是二分排序。
- 大于32的时候计算run的最小长度minrun(即开启合并的最小长度)返回的数要么小于16,要么是16,要么介于[16, 32]之间
- 把小于minrun的插入,大于minrun合并(当分区大于一的时候,如果分区大于三,如果后两个分区的和大于前一个分区,则中间的分区与最小的分区先合并,否则合并后两个分区)。
Collections.sort
其实就是调用timesort
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
浙公网安备 33010602011771号