概述
集合类中的sort方法,听说在java7中就引入了,但是我没有用过java7,不太清楚,java8中的排序是采用Timsort排序算法实现的,这个排序最开始是在python中由Tim Peters实现的,后来Java觉得不错,就引入了这个排序到Java中,竟然以作者的名字命名,搞得我还以为这个Tim是一个单词的意思,了不起,本文就从Arrays中实现的排序分析一下这个排序算法的原理,本文只会从源码角度分析,不会从算法角度去分析。
进入List中查看sort方法源码如下:
default void sort(Comparator super E>c) {
Object[] a= this.toArray();// 这个方法很简单,就是调用Arrays中的sort方法进行排序Arrays.sort(a, (Comparator) c);
ListIterator i = this.listIterator();for(Object e : a) {
i.next();
i.set((E) e);
}
}
进入Arrays.sort()方法
public static void sort(T[] a, Comparator super T>c) {//这个是自己传入的比较器如果为空,这里的a为存放数据的数组,那数组中的的元素都必须实现Comparator接口 if (c == null) {
sort(a);
}else{
//这个判断没有细看,不太清楚在判断什么if(LegacyMergeSort.userRequested)
legacyMergeSort(a, c);else
//这里就是所谓的TimSort了 TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
由于sort()和TimSort.sort走的流程基本一致,这里只分析TimSort.sort()方法,进入该方法。
这里有必要先说一下TimSort排序算法的核心内容,了解这个算法的核心内容有助于看下面的代码。
TimSort的核心是这样:
1.如果数组的长度小于32,直接采用二分法插入排序,就是下面方法中的binarySort方法实现的,这个算法原理,我举个例子大家就明白了
假设数组为:[1,3,9,6,2],二分法插入排序插入如下:
I:从开头先把自然升序(或降序)段找出来,那什么是自然升序段,就是没有经过排序算法,原始数据就是有序的,本数组中自然升序段就是1,3,9
II:按照正常的思维,直接拿6,从头开始和前三个元素一个一个比较也可以实现排序,但是这样效率太低,那怎么做可以效率高点呢?就是我们之前高数中学的二分查找法,就是通过二分查找法,我先找到3,发现6 > 3,那我就不用和1进行比较了。
2.如果数组的长度大于32,那就把数组拆分成一个一个的小段,每段的长度在16~32之间,使用上面介绍的二分法插入排序,把每一段进行排序,之后在把每一个排好序的段进行合并,最终就可以实现整个数组的排序,大致的思想就是这样,这个叙述可能会给大家一种误解,就是认为每一段都排好序之后在进行合并,其实不是这样的,而是每一段边排序,如果符合特定条件就会合并。
有了上面的了解,我们再来看下面的代码
static void sort(T[] a, int lo, int hi, Comparator super T>c,
T[] work,int workBase, intworkLen) {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//这里的MIN_MERGE就是32,如果数组长度小于32,直接采用二分法插入排序
//If array is small, do a "mini-TimSort" with no merges
if (nRemaining
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 ts = new TimSort<>(a, c, work, workBase, workLen);//<1.1> 这个就是计算分割之后每一段的长度的 int minRun =minRunLength(nRemaining);do{//Identify next run//<1.2> 寻找自然增长的结束位置 int runLen =countRunAndMakeAscending(a, lo, hi, c);//If run is short, extend to min(minRun, nRemaining)
if (runLen 对每一段进行二分法插入排序 binarySort(a, lo, lo + force, lo +runLen, c);
runLen=force;
}//Push run onto pending-run stack, and maybe merge,将每一段的起始位置和每一段的分段长度放入栈中
ts.pushRun(lo, runLen);//<1.4> 合并排好序的段,这个就是上面我说的并不是等所有的都排好序了再合并ts.mergeCollapse();//Advance to find next run
lo +=runLen;
// 将已经排好序的段,从总长度中减去
nRemaining-=runLen;
}while (nRemaining != 0);//Merge all remaining runs to complete sort
assert lo ==hi;//<1.5> 最终排序,这个方法在整个排序中只会执行一次ts.mergeForceCollapse();assert ts.stackSize == 1;
}
上面注释中<1.1>, minRunLength(nRemaining)
private static int minRunLength(intn) {assert n >= 0;int r = 0;