`

jdk7 Collections.sort()引发的IllegalArgumentException

    博客分类:
  • java
 
阅读更多

一  IllegalArgumentException的重现、解决

 package cn.com.common;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class ComparatorTest {

/**
*
* @ClassName: Student
* @Description:内部类,不直接对外提供
* @author linsky328
* @date 2017年6月30日 上午10:54:10
*
*/
private class Student {
private int id;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

}

public Student getStudent() {
return new Student();
}


/**
* 在 JDK7 版本及以上,Comparator 要满足如下三个条件,不然 Arrays.sort,
Collections.sort 会报 IllegalArgumentException 异常。
说明:
1) x,y 的比较结果和 y,x 的比较结果相反。
2) x>y,y>z,则 x>z。
3) x=y,则 x,z 比较结果和 y,z 比较结果相同。
* @param args
*/
public static void main(String[] args) {
int[] sample = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, 1, 0,
-2, 0, 0 };
List list = new ArrayList();
for (int i : sample) {
Student s = new ComparatorTest().getStudent();
s.setId(i);
list.add(s);
}

Collections.sort(list, new Comparator() {
public int compare(Student o1, Student o2) {
int id1 = o1.getId();
int id2 = o2.getId();
//return id1 > id2 ? 1 : -1;// throw java.lang.IllegalArgumentException
return id1==id2?0:id1 > id2 ? 1 : -1;
}
});
}
}

 

 


二 深入分析(转)
  

 

1.摘要

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

2.背景

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

 

 

出错部分的代码如下:

 

 

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

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

 

 

为了解开这个困惑,我们对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中的归并。归并过程相对普通的归并排序做了一定的优化,假如有如下的一段数组:

normal1

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

  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]值的段作为结果的结束部分。

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

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

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

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

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

  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指针左移两个位置。
    normal1

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

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

5.异常情况下Timsort的归并

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

 

 

  1. 仍然是分成A,B两段:
    normal1

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

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

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

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

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

注意!

在第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

分享到:
评论

相关推荐

    JDK7安装包.zip

    JDK7安装包.zip\JDK7安装包.zip\JDK7安装包.zip\JDK7安装包.zip\JDK7安装包.zip JDK7安装包.zip\JDK7安装包.zip\JDK7安装包.zip\JDK7安装包.zip\JDK7安装包.zip JDK7安装包.zip\JDK7安装包.zip\JDK7安装包.zip\JDK7...

    jdk-17.0.10-windows

    压缩包内容: Java SE Development Kit 17.0.10 (1)jdk-17.0.10_windows-x64_bin.exe (2)jdk-17.0.10_windows-x64_bin.msi (3)jdk-17.0.10_windows-x64_bin.zip

    jdk-11.0.15.1(jdk-11.0.15.1_osx-x64_bin.tar.gz )

    jdk-11.0.15.1(jdk-11.0.15.1_osx-x64_bin.tar.gz )适用于macOS x64 Compressed Archive系统:是一款Java 语言的软件开发工具包。JAVA JDK软件是整个Java的核心,不仅操作很简单,而且JAVA JDK有着实用、稳定、...

    最新版windows jdk-11.0.18-windows-x64-bin.zip

    **Java Development Kit (JDK) 11.0.18 for Windows 64位** JDK(Java Development Kit)是Oracle公司发布的用于开发Java应用程序的软件开发工具包,它是Java程序员编写、编译、调试和运行Java应用程序的必备工具。...

    最新版windows jdk-11.0.22-windows-x64-bin.zip

    最新版windows jdk-11.0.22_windows-x64_bin.zip最新版windows jdk-11.0.22_windows-x64_bin.zip最新版windows jdk-11.0.22_windows-x64_bin.zip最新版windows jdk-11.0.22_windows-x64_bin.zip

    最新版windows jdk-11.0.15_windows-x64_bin.exe

    最新版windows jdk-11.0.15_windows-x64_bin.exe最新版windows jdk-11.0.15_windows-x64_bin.exe

    最新版windows jdk-11.0.20-windows-x64-bin.exe

    最新版windows jdk-11.0.20_windows-x64_bin.exe最新版windows jdk-11.0.20_windows-x64_bin.exe最新版windows jdk-11.0.20_windows-x64_bin.exe

    最新版linux jdk-11.0.20-linux-x64-bin.tar.gz

    最新版linux jdk-11.0.20_linux-x64_bin.tar.gz最新版linux jdk-11.0.20_linux-x64_bin.tar.gz最新版linux jdk-11.0.20_linux-x64_bin.tar.gz

    最新官方jdk-11.0.15_windows-x64_bin

    标题“最新官方jdk-11.0.15_windows-x64_bin”指的是Java Development Kit (JDK) 的一个特定版本,即11.0.15,它针对Windows操作系统且为64位架构设计。这个JDK是官方发布的,确保了可靠性和安全性。 描述中的...

    最新版windows jdk-11.0.8_windows-x64_bin.zip

    《深入解析Windows JDK 11.0.8 64位安装与应用》 Windows JDK 11.0.8是Java开发工具包的一个重要版本,专为64位Windows操作系统设计,它提供了完整的Java开发环境,包括Java编译器、Java运行时环境以及各种开发工具...

    jdk_8.0.1310.11_64.exe -64位

    jdk_8.0.1310.11_64.exe -64位 可用! !!!!!!!

    Java-JDK-11.0.8(Windows &amp;amp; Mac os) 下载

    Java JDK 11.0.8 是Oracle公司发布的Java开发工具包的一个稳定版本,它针对开发者提供了完整的编译、调试和运行Java应用程序所需的环境。这个版本支持Windows和Mac OS操作系统,使得不同平台上的开发者都能方便地...

    jdk_8.0.1310.11_32bit.rar

    JDK是Java语言的软件开发工具包,主要用于移动设备,嵌入设备的应用程序,JDK(TM)7 32位是专为使用32位操作系统的朋友准备的。JDK是整个Java的核心,包括了Java运行环境、Java工具和Java基础的类库,不管是做Java...

    jdk-11.0.14_windows-x64_bin.exe

    这个版本的JDK是Java Standard Edition的重要组成部分,它包含了编译、调试、运行Java应用程序所需的所有工具和库。在本文中,我们将深入探讨JDK 11.0.14在Windows平台上的主要特性和使用。 一、Java SE 11概述 ...

    jdk_install.sh

    liunx 一键安装jdk脚本,实现jdk 一键安装 (bash jdk_install.sh)

    jdk-17.0.8(jdk-17-macos-x64-bin.dmg)

    jdk-17.0.8(jdk-17_macos-x64_bin.dmg)适用于macOS x64 系统

    jdk-11.0.21-windows

    压缩包内容: Java SE Development Kit 11.0.21 (1)jdk-11.0.21_windows-x64_bin.exe (2)jdk-11.0.21_windows-x64_bin.zip

    官方资源jdk-11.0.13_windows-x64_bin.zip

    https://www.oracle.com/java/technologies/downloads/#java11 现在下载JDK 要登录 才可以,这里转一次, 截止2021-12-22,目前jdk-11.0.13是最新版本。

    最新版windows jdk-17.0.4_windows-x64_bin.exe

    最新版windows jdk-17.0.4_windows-x64_bin.exe最新版windows jdk-17_windows-x64_bin.exe

    JDK_8.0.1310.11_32bit

    根据提供的标题、描述、标签及部分内容,我们可以了解到本主题主要关注的是Java Development Kit(Java开发工具包)版本为8.0.1310.11的32位版本(简称JDK 8 32位)。下面将详细介绍与该版本相关的知识点。 ### 一...

Global site tag (gtag.js) - Google Analytics