1.摘要

前一阵遇到了一个使用Collections.sort()时报异常的问题,跟小伙伴@zhuidawugui 一起排查了一下,发现问题的原因是JDK7的排序实现改为了TimSort,之后我们又进一步研究了一下这个神奇的算法。

2.背景

先说一下为什么要研究这个异常,前几天线上服务器发现日志里有偶发的异常:

 
1
2
3
4
5
6
7
8
9
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(TimSort.java:868)
  at java.util.TimSort.mergeAt(TimSort.java:485)
  at java.util.TimSort.mergeCollapse(TimSort.java:408)
at java.util.TimSort.sort(TimSort.java:214)
  at java.util.TimSort.sort(TimSort.java:173)
  at java.util.Arrays.sort(Arrays.java:659)
  at java.util.Collections.sort(Collections.java:217)
...

出错部分的代码如下:

 
1
2
3
4
5
6
7
List<Integer> list = getUserIds();
Collections.sort(list, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1>o2?1:-1;
    }
});

google了一下:JDK7中的Collections.Sort方法实现中,如果两个值是相等的,那么compare方法需要返回0,否则可能会在排序时抛错,而JDK6是没有这个限制的。

这个问题在测试时并没有出现,线上也只是小概率复现,如何稳定的复现这个问题?看了一下源代码,抛出异常的那段源代码让人根本摸不着头脑:

 
1
2
3
if (len2 == 0) {
    throw new IllegalArgumentException("Comparison method violates its general contract!");
}

为了解开这个困惑,我们对java实现的Timsort代码做了一些分析。

3.Timsort概述

TimSort排序是一种优化的归并排序,它将归并排序(merge sort) 与插入排序(insertion sort) 结合,并进行了一些优化。对于已经部分排序的数组,时间复杂度远低于 O(n log(n)),最好可达 O(n),对于随机排序的数组,时间复杂度为 O(nlog(n)),平均时间复杂度 O(nlog(n))。

它的整体思路是这样的:

  1. 遍历数组,将数组分为若干个升序或降序的片段,(如果是降序片段,反转降序的片段使其变为升序),每个片段称为一个Runtask
  2. 从数组中取一个RunTask,将这个RunTask压栈。
  3. 取出栈中相邻两个的RunTask,做归并排序,并将结果重新压栈。
  4. 重复(2),(3)过程,直到所有数据处理完毕。

这篇文章就不再过多的阐述Timsort整体思路了,有兴趣可以参考[译]理解timsort, 第一部分:适应性归并排序(Adaptive Mergesort)

4.Timsort的归并

重点说一下Timsort中的归并。归并过程相对普通的归并排序做了一定的优化,假如有如下的一段数组:

  1. 首先把数组拆成两个RunTask,这里称为A段和B段,注意,A段和B段在物理地址上是连续的:

  2. A段的起点为base1,剩余元素数量为len1;B段起点为base2,剩余元素数量为len2。取B点的起点值B[base2],在A段中进行二分查找,将A段中小于等于B[base2]的段作为merge结果的起始部分;再取A段的终点值a[base1 + len1 – 1],在B段中二分查找,将B段中大于等于a[base1 + len1 – 1]值的段作为结果的结束部分。

    更形象的说,这里把待归并的数据“掐头去尾”,只需要合并中间的数据就可以了:

  3. 之后需要创建一个tmp数组,大小为B段截取后的大小,并把B段剩余的数据拷贝过去,因为合并过程中这些数据会被覆盖掉。

    程序会记录corsor1和corsor2,这是待归并数据的指针,初始位置在A段和tmp段的末尾。同时会记录合并后数组的dest指针,位置在原B段的末尾。

    这里还有一个小优化:生成dest指针时会直接把A段cursor1指向的数据拷贝到B段末尾,同时cursor–,dest–。因为之前(2)步的时候已经保证了arr[cursor1]>arr[dest]

  4. 进行归并排序,这里每次归并比较时会记录A和tmp段比较“胜利(大于对方)”的次数,比较失败(小于对方)时会把胜利数清零。当有一个段的数据连续N次胜利时会激活另一个优化策略,在这里假设N为4,下图已经是A段连续胜利了4次的情况:

  5. 如果连续胜利N次,那么可以假设A段的数据平均大于B段,此时会用tmp[cursor2]的值在A[base0]至A[cursor1]中查找第一个小于tmp[cursor2]的索引k,并把A[k+1]到A[cursor1]的数据直接搬移到A[dest-len,dest]。

    对于例子中的数据,tmp[cursor2]=8,在A数组中查找到小于8的第一个索引(-1),之后把A[0,1]填充到A[dest-1,dest],cursor1和dest指针左移两个位置。

  6. 如果cursor1>=0,之后会再用curosr1指向的数据在tmp数组中查找,由于这里cursor1已经是-1了,循环结束。

  7. 最后把tmp里剩余的数据拷贝到A数组的剩余位置中,结束。

5.异常情况下Timsort的归并

假设这里实现的compare(obj o1,obj o2)如下:

 
1
2
3
public int compare(Integer o1, Integer o2) {
    return o1>o2?1:-1;
}
  1. 仍然是分成A,B两段:

  2. 在“掐头去尾”的时候,这时会有一些变化,程序执行到compare(B[base2],A[base1])时返回-1,A的左侧留下了两个应该被切走的“5”。

  3. 接下来是正常的归并过程。

  4. 这里同样会触发“胜利”>N次逻辑

  5. 在A[base1,cursor1]中查找小于tmp[cursor2]的元素,复制,cursor1和dest左移两位。

  6. 此时再用A[cursor1]在tmp中查找,tmp中所有的数据都被移入A数组,cursor2、dest左移4位。tmp2剩余元素的数量(len2)为0。

注意!

在第6步查找的时候,有A[base1+1]<tmp[0](tmp[0]的值等于没有合并之前的B[base2])。
而第2步时,有B[base2]<A[base1]
而最初生成RunTask的时候,有A[base1]<=A[base1+1]
连起来就是B[base2]<A[base1]<=A[base1+1]<B[base2],这显然是有问题的。

所以,当len2==0时,会抛出“Comparison method violates its general contract”异常。问题复现的条件是触发“胜利N次”的优化,并且存在类似(A[base1]==A[base1+x])&&(A[base1+x]==B[base2])的数据排列。这里应该还有几种另外的触发条件,精力有限,就不再深究了。

6.参考

TimSort in Java 7 OpenJDK 源代码阅读之 TimSort

解决方法:

Collections.sort(list1, new Comparator<Combo>(){
//重写排序规则
@Override
public int compare(Combo o1, Combo o2) {
if(o2.getCreateTime()!=null&&o1.getCreateTime()!=null){
if(o2.getCreateTime().getTime()>o1.getCreateTime().getTime()){
return 1;
}else if(o2.getCreateTime().getTime()<o1.getCreateTime().getTime()){
return -1;
}else{
return 0;
}
}
return 0;
};

JDK7的Comparison method violates its general contract异常的更多相关文章

  1. 排序遇到问题 JDK7的Comparison method violates its general contract

    图解JDK7的Comparison method violates its general contract异常 楼主分析的很详细,能力有限,我看得迷迷糊糊的,不过大致知道这个错误的起因了.学习了,谢 ...

  2. 关于jdk7中 使用Collections的排序方法时报Comparison method violates its general contract!异常

    参考: Comparison method violates its general contract Comparison method violates its general contract! ...

  3. Comparison method violates its general contract! 异常原因

    项目运行期间出现Comparison method violates its general contract!异常,网上查阅了一下,原因还是比较明确的: Collections.sort(list, ...

  4. java-collections.sort异常Comparison method violates its general contract!

    转载:http://www.tuicool.com/articles/MZreyuv 异常信息 java.lang.IllegalArgumentException: Comparison metho ...

  5. Comparison method violates its general contract

    生产环境出现的错误排查,错误log如下 java.lang.IllegalArgumentException: Comparison method violates its general contr ...

  6. Comparison method violates its general contract 解决

    java.lang.IllegalArgumentException: Comparison method violates its general contract! 原因 JDK7中的Collec ...

  7. 解决 Comparison method violates its general contract!

    问题:Comparison method violates its general contract!报错 Collections.sort(list, new Comparator<Integ ...

  8. 解决“Comparison method violates its general contract!”

    The ONE跑MaxProp.Prophet可能(取决于你JDK的版本)会报“java.lang.IllegalArgumentException: Comparison method violat ...

  9. [ Error 分析] Comparison method violates its general contract!

    public static void main(String[] args) { List<Long> ret = new ArrayList<>(); int n = 103 ...

随机推荐

  1. Ice的HelloWorld(Java)

    Ice是一种面向对象的中间间平台,入门ice,简单的HelloWorld是必不可少的. 转载请注明http://www.cnblogs.com/zrtqsk/p/3745286.html,谢谢. 一. ...

  2. Reverse Words in a String

    void reverseWords(string &s) { string res = "", tmp = ""; int l = s.length() ...

  3. centos7 安装nginx和php5.6.25遇到 无法访问php页面 报错file not found 问题解决

    php-fpm安装完成,nginx安装完成 netstap -ntl| 发下端口正常开启 iptables -L 返现9000端口已经开放 ps -aux|grep nginx 发下nginx进程正常 ...

  4. web安全——目录

    说明 写这个目录是为了方便阅读.也是为了记录统一的问题. 这个系列,并不一定是全的,也不一定是对的,所以请大家多做过滤. 这里面场景比较多的是本人在实践中遇到的问题,然后自己思考抽象的. 目录 web ...

  5. SQL复杂查询和视图

    子查询 现实中,很多情况下需要进行下述条件判断 某一元素是否是某一集合成员 某一集合是否包含另一集合 测试集合是否为空 测试集合是否存在另一元组 子查询是出现在WHERE子句中的SELECT语句被称为 ...

  6. 认真地搭建python开发环境

    面对不同python不同的版本以及各种各样的三方库,为了以后有必要学习一下怎样更好地搭建开发环境. python 2.7 作为控制台脚本 pycharm下: python 3.4 python 2.7 ...

  7. PHP-- 三种数据库随机查询语句写法

    1. Oracle,随机查询查询语句-20条 select * from (  select  *  from 表名 order by dbms_random.value ) where rownum ...

  8. idea 新建web项目

  9. IntelliJ_设置

    1.修改背景色.修改字体大小 http://blog.csdn.net/hpf911/article/details/16888797 2.显示行号 搜索Line Number 3.代码结构图 Vie ...

  10. ActiveMQ(七)_伪集群和主从高可用使用

      一.本文目的         介绍如何在同一台虚拟机上搭建高可用的Activemq服务,集群数量包含3个Activemq,当Activemq可用数>=2时,整个集群可用.         本 ...