在工作的时候用到了对泛型集合排序,本来以为里面也是像排序数组一样使用双轴快排,但是跟着Collections.sort()源码里发现是用了TimSort.sort()的排序,去网上简单的搜索了一下,说TimSort用了归并排序,并极大程度的利用了自然界很多数都已经拍好序了这个规律, 其中比较好的文章是:世界上最快的排序算法——Timsort,我在看完源码之后虽然看"懂"了,TimSort它是怎么做的,但是不清楚它为什么这么做,这篇文章就解答了我很多疑惑。为了帮助大家更好的理解TimSort,也为了让我记录一下Timsort的笔记,所以我写了这篇博客来和大家分享一下我对JDK1.8版本源码TimSort的阅读心得。
我们先跟Collections.sort()进去发现是调用传进来集合的list.sort(),代码如下。可以看到主要干了三件事情,先是调用本身的toArray()方法生成一个数组,再调用Arrays.sort()方法对数组进行排序,最后将排序好的结果再塞回list里
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);
}
}
下面是Arrays.sort()的代码,可以看到如果没有传比较器的话就会调用ComparableTimSort.sort(),如果传了比较器则直接使用Tim.sort(), 至于legacyMergeSort(a, 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
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
我们直接跟进去TimSort.sort()方法,ComparableTimSort里的sort方法逻辑是和Timsort一样的,只不过是元素间进行比较的时候有区别而已。
这个方法是完成整个排序的核心方法,所有的排序都会在这个方法里完成。
改方法里调用的其它方法我都会下面进行详细讲解。
static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
T[] work, int workBase, int workLen) {
assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;
int nRemaining = hi - lo;
if (nRemaining < 2)
return; // Arrays of size 0 and 1 are always sorted
// If array is small, do a "mini-TimSort" with no merges
// private static final int MIN_MERGE = 32;
// 如果要排序的部分小于这个最小的阈值,则进行二分插值排序
if (nRemaining < MIN_MERGE) {
// 首先计算出数组刚开始已经排序好的部分
int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
// 然后对剩下的部分进行排序
binarySort(a, lo, hi, lo + initRunLen, c);
return;
}
/**
* March over the array once, left to right, finding natural runs,
* extending short natural runs to minRun elements, and merging runs
* to maintain stack invariant.
*/
// 接下来构造一个Timsort
TimSort<T> ts = new TimSort<>(a, c, work, workBase, workLen);
// 计算出单个run的最小长度,run可以看出是原数组的一个排序段,每个run内部都是升序的
int minRun = minRunLength(nRemaining);
do {
// Identify next run
// 首先计算出数组刚已经排序好的部分
int runLen = countRunAndMakeAscending(a, lo, hi, c);
// If run is short, extend to min(minRun, nRemaining)
// 如果数组中有序部分的长度无法满足run的最小长度
if (runLen < minRun) {
// 取还需要排序的字段和minRun 的最小值
int force = nRemaining <= minRun ? nRemaining : minRun;
// 对force部分进行二分插值排序
binarySort(a, lo, lo + force, lo + runLen, c);
runLen = force;
}
// Push run onto pending-run stack, and maybe merge
// 至此run里所有的元素都是升序的,将该run放在栈中
ts.pushRun(lo, runLen);
// 合并run
ts.mergeCollapse();
// Advance to find next run
// 移动lo,寻找下一个run
lo += runLen;
// 更新还需要排序的长度
nRemaining -= runLen;
} while (nRemaining != 0);
// Merge all remaining runs to complete sort
assert lo == hi;
// 强制合并剩余的所有run
ts.mergeForceCollapse();
assert ts.stackSize == 1;
}
countRunAndMakeAscending方法,从这个方法就可以看出TimSort非常相信在一个数组里,有很多段是有序的,无论它是降序还是升序。
private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,
Comparator<? super T> c) {
// 断言校验传进的值合法
assert lo < hi;
int runHi = lo + 1;
if (runHi == hi)
return 1;
// 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++;
}
return runHi - lo;
}
/**
* 一个很简单的左右指针反转数组元素方法
*/
private static void reverseRange(Object[] a, int lo, int hi) {
hi--;
while (lo < hi) {
Object t = a[lo];
a[lo++] = a[hi];
a[hi--] = t;
}
}
binarySort方法
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).
*/
// 接着在已经排序好的字段区间里通

最低0.47元/天 解锁文章
493

被折叠的 条评论
为什么被折叠?



