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則采用歸並排序。