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. 链路层的简介和MTU

    链路层杂谈(凭个人理解瞎说的,欢迎拍砖) 链路层,说白了就是把网络层的IP数据处理一下,加点东西,放到物理层上去.    加的东西:源.目的地址和CRC校验值,有的还有类型这个字段,用来区分协议.  ...

  2. 读书摘要,Hackable Projects

    完整读完Google的三篇谈Hackable Projects的文章,以及一篇从Test Pyramid看UnitTest的比重.一篇谈Optimal Logging的文章,感觉这5篇在测试.日志两个 ...

  3. 谱多流形聚类SMMC

    今天是2015年的最后一天,决定尽量乘着这三天休息把毕设主题的博客给更完,今天写smmc的算法,接下来三天会对前面的三个算法kmeans.SC以及smmc应用在今年的研究生建模提供的数据中进行matl ...

  4. FineUI v3.3.2发布!目前最稳定版本,五年陈酿!

    关于FineUI基于 ExtJS 的专业 ASP.NET 控件库. FineUI的使命创建 No JavaScript,No CSS,No UpdatePanel,No ViewState,No We ...

  5. 解决Package illuminate/html is abandoned, you should avoid using it. Use laravelcollective/html instead.问题

    解决步骤: 1.分析问题是因为laravel5.1不赞成使用illuminate/html而推荐使用laravelcollective/html包,所以我们利用composer命令移除illumina ...

  6. 刷新SqlServer所有视图【存储过程】

    摘自:http://www.cnblogs.com/yashen/archive/2004/12/23/81000.html CREATE PROCEDURE RefreshAllView AS DE ...

  7. .NET平台下的微信SDK(Rabbit.WeiXin)开源发布

    在上一篇文章<RabbitHub开源情况及计划>上有提及到了一个新的开源项目——微信SDK,经过几天的努力现在开源发布Beta1版本. 目录 前言 特点 功能 支持的消息类型 请求消息 事 ...

  8. [POJ3696]The Luckiest number(数论)

    题目:http://poj.org/problem?id=3696 题意:给你一个数字L,你要求出一个数N,使得N是L的倍数,且N的每位数都必须是8,输出N的位数(如果不存在输出0) 分析: 首先我们 ...

  9. 利用ZTree链接数据库实现 [权限管理]

    最近想研究权限管理,看群里有人发了ZTrees模板,我看了下,觉得笔easyUI操作起来更灵活些,于是就开始研究了. 刚开始从网上找了找了个Demo,当然这个并没有实现权限啥的,但实现了前台调用Aja ...

  10. XML是什么东西

    记住,XML就是为数据传输而设计的一种标记语言,也是特么的一种标记语言,在这点上,和html是有点类似的,你看<xml>和<html>看上去难道不是很像嘛,而html是为数据显 ...