• 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则采用归并排序。

    面朝大海,春暖花开。
  • 相关阅读:
    05Mybatis_入门程序——根据id查询用户
    04Mybatis_搭建Mybatis的开发环境
    03Mybatis_mybatis框架原理——执行流程
    02Mybatis_原生态jdbc编程中的问题总结——从而引生出为什么要用Mybatis
    01Mybatis_课程安排
    21SpringMvc_异步发送表单数据到Bean,并响应JSON文本返回(这篇可能是最重要的一篇了)
    20SpringMvc_结果的转发可共享参数;重定向不能共享参数
    19SpringMvc_在业务控制方法中收集List集合中包含JavaBean参数
    18SpringMvc_在业务控制方法中收集数组参数
    阿里架构师,讲述基于微服务的软件架构模式(附资料)
  • 原文地址:https://www.cnblogs.com/HapLe0/p/8005953.html
Copyright © 2020-2023  润新知