源码分析:

Collections.sort中:

 
 public static <T extends Comparable<? super T>> void sort(List<T> list) {
        Object[] a = list.toArray();
        Arrays.sort(a);
        ListIterator<T> i = list.listIterator();
        for (int j=0; j<a.length; j++) {
            i.next();
            i.set((T)a[j]);
        }
    }

可以发现,最终还是使用了Arrays.sort(a);的,不同的是:一个针对数组,一个针对集合

扩展:不同版本的内部实现问题

JDK1.6以下的时候:调用sort方法时,默认是使用mergeSort的算法
JDK1.7后,使用TimSort的算法。源码如下:
JDK7的sort方法:

 
public static void sort(Object[] a) {  
        if (LegacyMergeSort.userRequested)  
            legacyMergeSort(a);  
        else  
            ComparableTimSort.sort(a);  
    }  
    /** To be removed in a future release. */  
    private static void legacyMergeSort(Object[] a) {  
        Object[] aux = a.clone();  
        mergeSort(aux, a, 0, a.length, 0);  
    }

JDK6以下的sort方法:

 
public static void sort(Object[] a) {  
        Object[] aux = (Object[])a.clone();  
        mergeSort(aux, a, 0, a.length, 0);  
    }

当然可以使用下列的方式,在JDK7依旧使用mergeSort算法:

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

但是,从注释中可以发现,以后将让TimSort代替mergeSort

根据这篇JDK7中的排序算法详解—Collections.sort和Arrays.sort找到了解决方法:

而在Java 7中,内部实现换成了TimSort,其对对象间比较的实现要求更加严格:
Comparator的实现必须保证以下几点(出自这儿)):

  1. sgn(compare(x, y)) == -sgn(compare(y, x))
  2. (compare(x, y)>0) && (compare(y, z)>0) 意味着 compare(x, z)>0
  3. compare(x, y)==0 意味着对于任意的z:sgn(compare(x, z))==sgn(compare(y, z)) 均成立

所以,compare要相应的改成:

 
public int compare(ComparatorTest o1, ComparatorTest o2) {
    return o1.getValue() == o2.getValue() ? 0 : 
                (o1.getValue() > o2.getValue() ? 1 : -1);
}

先对相等的情况判断,再对大小的判断。

算法分析

既然这个算法比之前快排要快,那么肯定有它的巧妙之处,我们来仔细看看吧。

算法步骤

1.对于很小的数组(长度小于27),会使用插入排序。
2.选择两个点P1,P2作为轴心,比如我们可以使用第一个元素和最后一个元素。
3.P1必须比P2要小,否则将这两个元素交换,现在将整个数组分为四部分:
(1)第一部分:比P1小的元素。
(2)第二部分:比P1大但是比P2小的元素。
(3)第三部分:比P2大的元素。
(4)第四部分:尚未比较的部分。
在开始比较前,除了轴点,其余元素几乎都在第四部分,直到比较完之后第四部分没有元素。
4.从第四部分选出一个元素a[K],与两个轴心比较,然后放到第一二三部分中的一个。
5.移动L,K,G指向。
6.重复 4 5 步,直到第四部分没有元素。
7.将P1与第一部分的最后一个元素交换。将P2与第三部分的第一个元素交换。
8.递归的将第一二三部分排序。

图表演示

注:图片来自Vladimir Yaroslavskiy的论文。

算法源码

  1. //对外公开的两个sort方法
  2. public static void sort(int[] a) {
  3. sort(a, 0, a.length);
  4. }
  5. public static void sort(int[] a, int fromIndex, int toIndex) {
  6. rangeCheck(a.length, fromIndex, toIndex);
  7. dualPivotQuicksort(a, fromIndex, toIndex - 1, 3);
  8. }
  9. //对数组的边界检测
  10. private static void rangeCheck(int length, int fromIndex, int toIndex) {
  11. if (fromIndex > toIndex) {
  12. throw new IllegalArgumentException("fromIndex > toIndex");
  13. }
  14. if (fromIndex < 0) {
  15. throw new ArrayIndexOutOfBoundsException(fromIndex);
  16. }
  17. if (toIndex > length) {
  18. throw new ArrayIndexOutOfBoundsException(toIndex);
  19. }
  20. }
  21. //交换数组中两个元素
  22. private static void swap(int[] a, int i, int j) {
  23. int temp = a[i];
  24. a[i] = a[j];
  25. a[j] = temp;
  26. }
  27. /**
  28. * 双轴快排的具体实现
  29. * @param a     待排序数组
  30. * @param left  数组需排序上界
  31. * @param right 数组需排序下界
  32. * @param div   理解为从何位置取轴
  33. */
  34. private static void dualPivotQuicksort(int[] a, int left,int right, int div) {
  35. int len = right - left;
  36. //数组长度如果很小(27),则直接用插入排序对其排序
  37. if (len < 27) {
  38. for (int i = left + 1; i <= right; i++) {
  39. for (int j = i; j > left && a[j] < a[j - 1]; j--) {
  40. swap(a, j, j - 1);
  41. }
  42. }
  43. return;
  44. }
  45. //取到位于1/div和div-1/div位置的点,并用他们来做轴
  46. int third = len / div;
  47. int m1 = left + third;
  48. int m2 = right - third;
  49. if (m1 <= left) {
  50. m1 = left + 1;
  51. }
  52. if (m2 >= right) {
  53. m2 = right - 1;
  54. }
  55. //确保left是小的,right是大的
  56. if (a[m1] < a[m2]) {
  57. swap(a, m1, left);
  58. swap(a, m2, right);
  59. }
  60. else {
  61. swap(a, m1, right);
  62. swap(a, m2, left);
  63. }
  64. // 两个轴
  65. int pivot1 = a[left];
  66. int pivot2 = a[right];
  67. // 代表比p1小和比p2大的两个指针
  68. int less = left + 1;
  69. int great = right - 1;
  70. // 开始取出less到great之间的未知大小数据,与两个轴比较
  71. // 并且将数据放入正确的区域后调整各个指针
  72. for (int k = less; k <= great; k++) {
  73. //如果取出的数比p1小,那么直接到less左侧,并且less右移
  74. if (a[k] < pivot1) {
  75. swap(a, k, less++);
  76. }
  77. //如果取出的数比p2大,那么首先确定great左侧没有比p2大的数
  78. //然后与great位置的数字交换,great左移
  79. //此时,great交换的数字肯定是比p2小或者相等的(首先确定过)
  80. //那么此时再与p1相比,处理这个数的区间
  81. else if (a[k] > pivot2) {
  82. while (k < great && a[great] > pivot2) {
  83. great--;
  84. }
  85. swap(a, k, great--);
  86. if (a[k] < pivot1) {
  87. swap(a, k, less++);
  88. }
  89. }
  90. //如果这个数比p1大但是比p2小,则不需要交换,只需将k指针右移
  91. }
  92. //将p1与less左侧的第一个数交换
  93. swap(a, less - 1, left);
  94. //将p2与great右侧的第一个数交换
  95. swap(a, great + 1, right);
  96. // 计算出在两轴大小之间的个数
  97. int dist = great - less;
  98. //如果这个数很小(13),那么取轴的点向两边偏
  99. if (dist < 13) {
  100. div++;
  101. }
  102. // 对三个子区间分别排序,因为less-1和great+1是轴,已经排好了序
  103. // 所以不需要比较
  104. dualPivotQuicksort(a, left, less - 2, div);
  105. dualPivotQuicksort(a, great + 2, right, div);
  106. // 如果在中间区间的数字很多,那么排除掉一些相等的元素再进行排序
  107. if (dist > len - 13 && pivot1 != pivot2) {
  108. for (int k = less; k <= great; k++) {
  109. if (a[k] == pivot1) {
  110. swap(a, k, less++);
  111. }
  112. else if (a[k] == pivot2) {
  113. swap(a, k, great--);
  114. if (a[k] == pivot1) {
  115. swap(a, k, less++);
  116. }
  117. }
  118. }
  119. }
  120. // 对中间的区间排序
  121. if (pivot1 < pivot2) {
  122. dualPivotQuicksort(a, less, great, div);
  123. }
  124. }

总结

双轴排序利用了区间相邻的特性,对原本的快排进行了效率上的提高,很大程度上是利用了数学的一些特性,果然,算法跟数学还是息息相关的吖。

jdk1.6与jdk1.7list集合排序区别与算法的更多相关文章

  1. 记一次诡异的bug调试——————关于JDK1.7和JDK1.8中HashSet的hash(key)算法的区别

    现象: 测试提了一个bug,我完全复现不了,但是最吊诡的是在其他人的机器上都可以复现.起初以为是SVN合并后出现的冲突,后来经过对比法排查: step 1: 我本地开两个jetty,一个跑合并之前的版 ...

  2. 集合排序 Comparator和Comparable的使用区别

    Java 排序 Compare  Comparator接口 Comparable接口 区别 在Java中使用集合来存储数据时非常常见的,集合排序功能也是常用功能之一.下面看一下如何进行集合排序,常用的 ...

  3. Jdk1.7 与 jdk1.8的区别,最新的特征有哪些(美团,360,京东面试题目)

    在jdk7的新特性方面主要有下面几方面的增强: 1.1二进制变量的表示,支持将整数类型用二进制来表示,用0b开头. 所有整数int.short.long.byte都可以用二进制表示: byte aBy ...

  4. java经常看见 jdk5 jdk1.5 —— jdk6 jdk1.6 这两者有什么区别吗?

    问.java经常看见 jdk5 jdk1.5 —— jdk6 jdk1.6 这两者有什么区别吗? 答:没有区别,jdk5 和 jdk1.5 所代表的意思是一样的,只是叫法不一样 关键字: jdk5 j ...

  5. 集合排序Comparable和Comparator有什么区别?

    Comparable和Comparator兄弟俩长得是真像.但是,需要注意下,使用中它们还是有不少区别的.下面,就一探究竟吧. 一.Comparator 做过集合排序的童鞋应该知道,可以使用Colle ...

  6. 牛客网Java刷题知识点之HashMap的实现原理、HashMap的存储结构、HashMap在JDK1.6、JDK1.7、JDK1.8之间的差异以及带来的性能影响

    不多说,直接上干货! 福利 => 每天都推送 欢迎大家,关注微信扫码并加入我的4个微信公众号:   大数据躺过的坑      Java从入门到架构师      人工智能躺过的坑          ...

  7. CopyOnWriteArrayList集合排序异常问题

    1.集合自定义排序实现 对List集合的自定义排序想必大家都知道要使用如下的方式,通过实现Comparator接口并实现compare方法来实现. /** * * @方法名 changeChain * ...

  8. hashMap在jdk1.7与jdk1.8中的原理及不同

    在分析jdk1.7中HashMap的hash冲突时,不知大家是否有个疑问就是万一发生碰撞的节点非常多怎么版?如果说成百上千个节点在hash时发生碰撞,存储一个链表中,那么如果要查找其中一个节点,那就不 ...

  9. Java集合排序及java集合类详解--(Collection, List, Set, Map)

    1         集合框架 1.1         集合框架概述 1.1.1         容器简介 到目前为止,我们已经学习了如何创建多个不同的对象,定义了这些对象以后,我们就可以利用它们来做一 ...

随机推荐

  1. AtCoder Regular Contest 070F:Honest Or Unkind

    题目传送门:https://arc070.contest.atcoder.jp/tasks/arc070_d 题目翻译 有\(n\)个人,其中有\(a\)个人是诚实的,另外\(b\)个是不诚实的.你可 ...

  2. Mysql常用命令行大全(四)外键及其它

    表构成 mysql> show tables; +----------------------+| Tables_in_WebComplie |+----------------------+| ...

  3. KCF+Opencv3.0+Cmake+Win10 测试

    配置 需要的文件下载 安装CMake,安装opencv3.0.0 在KCFcpp-master 目录下新建一个文件夹,命名为build 打开CMake-GUI配置如下: 点击Configure,编译器 ...

  4. JS---设计简易日历

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  5. [poj3107/poj2378]Godfather/Tree Cutting树形dp

    题意:求树的重心(删除该点后子树最大的最小) 解题关键:想树的结构,删去某个点后只剩下它的子树和原树-此树所形成的数,然后第一次dp求每个子树的节点个数,第二次dp求解答案即可. 此题一开始一直T,后 ...

  6. 一步步实现 Prism + MEF(一)--- 搭建框架

    第一步:构建一个名为Bootstrapper的类作为引导程序. class Bootstrapper : MefBootstrapper { } 第二步:在MainWindow窗体中添加一个Coont ...

  7. vmware中centos6.5无法启动拷贝出里面的资料的方法

    先说一下我的环境:windows7-x64位机器下安装的vmware虚拟机,里面安装的是centos6.5-x64位的系统. 系统崩溃的原因:从cenos拖拽一个文件到win7下,结果就卡死了.整个系 ...

  8. 6. webshell文件上传分析溯源

    这道题也是借助大佬的帮助才成功,具体我们来看: 既然人家扫描发现后台目录有文件上传,我们也不能落后,顺便拿出了传说中的御剑,并进行一波扫描: 发现了几个比较有用的目录,特别是upload1.php跟u ...

  9. C++11之lambda表达式应用

    应用 foreach语句中 #include <time.h> #include <algorithm> using namespace std; void func(int ...

  10. SCUT - 12 - 西方国家 - 矩阵快速幂

    https://scut.online/p/12 可以用矩阵快速幂来做. #include<bits/stdc++.h> using namespace std; typedef long ...