Java集合排序功能实现分析

Posted 喜东东

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java集合排序功能实现分析相关的知识,希望对你有一定的参考价值。

Java如何实现集合的排序?

- 本文以对Student对象集合为例进行排序
Java通过Collections.sort(List<Student> stuList)和Collections.sort(List<Student> stuList,Comparator c)两种方法实现排序。

用Collections.sort(List list) 方法实现排序:

step1: 确保Student类实现了Comparable接口,并重写了compareTo()方法。

step2:调用Collections.sort(List list) 方法进行排序。

技术分享图片
 1 public class Student implements Comparable<Student> {
 2 
 3     private int age;
 4 
 5     public Student(int age) {
 6         this.age = age;
 7     }
 8 
 9     public int getAge() {
10         return age;
11     }
12 
13     @Override
14     public int compareTo(Student student) {  // 重写compareTo方法
15 
16         return (this.age < student.age) ? -1 : ((this.age == student.age) ? 0 : 1);
17     }
18 
19 
20     public static void main(String[] args) {
21         List<Student> stuList = new ArrayList();
22         stuList.add(new Student(5));
23         stuList.add(new Student(3));
24         stuList.add(new Student(7));
25         stuList.add(new Student(2));
26         stuList.add(new Student(4));
27         stuList.add(new Student(6));
28         stuList.add(new Student(1));
29 
30         Collections.sort(stuList);  // 调用排序方法
31 
32         for (Student student : stuList) {
33             System.out.println(student.getAge());
34         }
35     }
36 }
技术分享图片

原理分析:

step1: Collections类调用List.sort(Comparator c)方法,比较器c赋值为null.

1     public static <T extends Comparable<? super T>> void sort(List<T> list) {
2         list.sort(null);
3     }

step2: List接口中的sort方法将stuList集合转换成数组,通过Arrays.sort()方法对其进行排序,并将排序后的元素替换stuList中每个元素。

技术分享图片
1    default void sort(Comparator<? super E> c) {
2         Object[] a = this.toArray();
3         Arrays.sort(a, (Comparator) c);
4         ListIterator<E> i = this.listIterator();
5         for (Object e : a) {
6             i.next();
7             i.set((E) e);
8         }
9     }
技术分享图片

那到底时在哪里调用的compareTo方法的呢?

进入Arrays.sort()方法:

技术分享图片
 1     public static <T> void sort(T[] a, Comparator<? super T> c) {
 2         if (c == null) {
 3             sort(a);
 4         } else {
 5             if (LegacyMergeSort.userRequested)
 6                 legacyMergeSort(a, c);
 7             else
 8                 TimSort.sort(a, 0, a.length, c, null, 0, 0);
 9         }
10     }
技术分享图片

没有制定比较器,因此c==null为true,执行sort(a)方法:

1 public static void sort(Object[] a) {
2         if (LegacyMergeSort.userRequested)
3             legacyMergeSort(a);
4         else
5             ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
6     }

LegacyMergeSort.userRequested默认为false,表示是否使用传统归并排序,传统归并排序在1.5及之前是默认排序方法,1.5之后默认执行ComparableTimSort.sort()方法。除非程序中强制要求使用传统归并排序。语句如下:

System.setProperty("java.util.Arrays.useLegacyMergeSort", "true"); 

所以继续看ComparableTimSort.sort()方法:

技术分享图片
 1     static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {
 2         assert a != null && lo >= 0 && lo <= hi && hi <= a.length;
 3 
 4         int nRemaining  = hi - lo;
 5         if (nRemaining < 2)
 6             return;  // Arrays of size 0 and 1 are always sorted
 7 
 8         // If array is small, do a "mini-TimSort" with no merges
 9         if (nRemaining < MIN_MERGE) {
10             int initRunLen = countRunAndMakeAscending(a, lo, hi);
11             binarySort(a, lo, hi, lo + initRunLen);
12             return;
13         }
14 
15         /**
16          * March over the array once, left to right, finding natural runs,
17          * extending short natural runs to minRun elements, and merging runs
18          * to maintain stack invariant.
19          */
20         ComparableTimSort ts = new ComparableTimSort(a, work, workBase, workLen);
21         int minRun = minRunLength(nRemaining);
22         do {
23             // Identify next run
24             int runLen = countRunAndMakeAscending(a, lo, hi);
25 
26             // If run is short, extend to min(minRun, nRemaining)
27             if (runLen < minRun) {
28                 int force = nRemaining <= minRun ? nRemaining : minRun;
29                 binarySort(a, lo, lo + force, lo + runLen);
30                 runLen = force;
31             }
32 
33             // Push run onto pending-run stack, and maybe merge
34             ts.pushRun(lo, runLen);
35             ts.mergeCollapse();
36 
37             // Advance to find next run
38             lo += runLen;
39             nRemaining -= runLen;
40         } while (nRemaining != 0);
41 
42         // Merge all remaining runs to complete sort
43         assert lo == hi;
44         ts.mergeForceCollapse();
45         assert ts.stackSize == 1;
46     }
技术分享图片

line4的nRemaining表示没有排序的对象个数,方法执行前,如果这个数小于2,就不需要排序了。

如果2<= nRemaining <=32,即MIN_MERGE的初始值,表示需要排序的数组是小数组,可以使用mini-TimSort方法进行排序,否则需要使用归并排序。

mini-TimSort排序方法:先找出数组中从下标为0开始的第一个升序序列,或者找出降序序列后转换为升序重新放入数组,将这段升序数组作为初始数组,将之后的每一个元素通过二分法排序插入到初始数组中。注意,这里就调用到了我们重写的compareTo()方法了。

获取初始数组的方法:

技术分享图片
 1     private static int countRunAndMakeAscending(Object[] a, int lo, int hi) {
 2         assert lo < hi;
 3         int runHi = lo + 1;
 4         if (runHi == hi)
 5             return 1;
 6 
 7         // Find end of run, and reverse range if descending
 8         if (((Comparable) a[runHi++]).compareTo(a[lo]) < 0) { // Descending
 9             while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) < 0)
10                 runHi++;
11             reverseRange(a, lo, runHi);
12         } else {                              // Ascending
13             while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) >= 0)
14                 runHi++;
15         }
16 
17         return runHi - lo;
18     }
技术分享图片

根据程序中举例,a[1].compareTo(a[0]) <0,所以向下循环查看a[2].compareTo(a[1]) <0、a[3].compareTo(a[2]) <0等等是否成立,我们发现a[2].compareTo(a[1]) <0不成立,所以循环终止,获取到最长的降序数组为a[]{5,3},再调用reverseRange()方法将其升序排列为a[]{3,5},作为初始数组,initRunLen=2。随后进行二分法插入操作,代码如下:

技术分享图片
 1 private static void binarySort(Object[] a, int lo, int hi, int start) {
 2         assert lo <= start && start <= hi;
 3         if (start == lo)
 4             start++;
 5         for ( ; start < hi; start++) {
 6             Comparable pivot = (Comparable) a[start];
 7 
 8             // Set left (and right) to the index where a[start] (pivot) belongs
 9             int left = lo;
10             int right = start;
11             assert left <= right;
12             /*
13              * Invariants:
14              *   pivot >= all in [lo, left).
15              *   pivot <  all in [right, start).
16              */
17             while (left < right) {
18                 int mid = (left + right) >>> 1;
19                 if (pivot.compareTo(a[mid]) < 0)
20                     right = mid;
21                 else
22                     left = mid + 1;
23             }
24             assert left == right;
25 
26             /*
27              * The invariants still hold: pivot >= all in [lo, left) and
28              * pivot < all in [left, start), so pivot belongs at left.  Note
29              * that if there are elements equal to pivot, left points to the
30              * first slot after them -- that‘s why this sort is stable.
31              * Slide elements over to make room for pivot.
32              */
33             int n = start - left;  // The number of elements to move
34             // Switch is just an optimization for arraycopy in default case
35             switch (n) {
36                 case 2:  a[left + 2] = a[left + 1];
37                 case 1:  a[left + 1] = a[left];
38                          break;
39                 default: System.arraycopy(a, left, a, left + 1, n);
40             }
41             a[left] = pivot;
42         }
43     }
技术分享图片

循环下标>=2的所有元素,通过二分法将其插入到初始数组中的适当位置,这样,通过调用元素的compareTo()方法进行排序的功能实现完毕。

用Collections.sort(List list,Comparator c) 方法实现排序:

该方法传入一个比较器,用于比较各元素的大小。该方法不需要元素实现Comparable接口,但需要一个实现Comparator接口的实现类来实例化一个比较器,注意,这里的Comparator是一个接口而非类。这里通常采用匿名内部类的方法。

1 Collections.sort(stuList, new Comparator<Student>() {
2             @Override
3             public int compare(Student stu1, Student stu2) {
4                 return (stu1.getAge() < stu2.getAge()) ? -1 : (stu1.getAge() == stu2.getAge() ? 0 : 1);
5             }
6         });

这种方法实现排序的方式与上述方法基本相同。

先调用Collections.sort()方法,传入集合和比较器,sort()方法调用List的sort方法,传入比较器。(同上step1)代码如下:

1     public static <T> void sort(List<T> list, Comparator<? super T> c) {
2         list.sort(c);
3     }

 

List中sort()方法调用Arrays.sort()方法,传入数组和比较器。(同上step2)

技术分享图片
1 default void sort(Comparator<? super E> c) {
2     Object[] a = this.toArray();
3     Arrays.sort(a, (Comparator) c);
4     ListIterator<E> i = this.listIterator();
5     for (Object e : a) {
6         i.next();
7         i.set((E) e);
8     }
9 }
技术分享图片

Arrays.sort方法调用TimSort.sort()方法,代码如下:

技术分享图片
 1 public static <T> void sort(T[] a, Comparator<? super T> c) {
 2         if (c == null) {
 3             sort(a);
 4         } else {
 5             if (LegacyMergeSort.userRequested)
 6                 legacyMergeSort(a, c);
 7             else
 8                 TimSort.sort(a, 0, a.length, c, null, 0, 0);
 9         }
10     }
技术分享图片

legacyMergeSort(a,c)和TimSort.sort()方法中与方法一不同的地方只有一点,即方法一中使用a.compareTo(b)进行比较而方法二中使用comparator.compare(a,b)进行比较,其他均相同。

技术分享图片
 1 private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,
 2                                                     Comparator<? super T> c) {
 3         assert lo < hi;
 4         int runHi = lo + 1;
 5         if (runHi == hi)
 6             return 1;
 7 
 8         // Find end of run, and reverse range if descending
 9         if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
10             while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
11                 runHi++;
12             reverseRange(a, lo, runHi);
13         } else {                              // Ascending
14             while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
15                 runHi++;
16         }
17 
18         return runHi - lo;
19     }
技术分享图片

总结:

1.Collections.sort()排序有两种实现方式,一是让元素类实现Comparable接口并覆盖compareTo()方法,二是给Collecitons.sort()方法传入比较器,通常采用匿名内部内的方式传入。

2.Collections.sort()通过调用Arrays.sort()方法进行排序,在Java1.6+中,如果集合大小<32则采用Tim-Sort算法,如果>=32则采用归并排序。

 


以上是关于Java集合排序功能实现分析的主要内容,如果未能解决你的问题,请参考以下文章

Java集合与数据结构 排序

Java集合与数据结构 排序

Java集合与数据结构 排序

Java集合与数据结构——七大排序算法的实现

7.Java集合-Arrays类实现原理及源码分析

Java方法