背景
最近,项目正在集成测试阶段,项目在服务器上运行了一段时间,点击表格的列进行排序的时候,有的列排序正常,有的列在排序的时候,在后台会抛出如下异常,查询到不到数据,而且在另外一台服务器上是可以排序的, 就是说该问题是偶现的;
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeLo(TimSort.java:747)
at java.util.TimSort.mergeAt(TimSort.java:483)
at java.util.TimSort.mergeCollapse(TimSort.java:410)
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)
google之后,发现有很多的人也遇到了这个问题,原因就是 JDK 升级的问题,也才想起来最近项目也升级了JDK, 从 JDK1.6 改为JDK1.7了,
原因
在 JDK1.6 和 JDK1.7 的 Collections.sort(List, Comparator) 方法中,底层的实现变了,在使用Comparator比较器进行比较的时候,可能会返回-1,1和0三种结果,但在平时的时候,往往会忽略0,也就是两个元素相等的情况或者NULL的情况,如果在调用Comparator比较的时候,只会返回-1或1,则在 JDK1.6的时候,可以正常运行,在升级到 JDK1.7之后,有可能会运行失败,会抛出异常:java.lang.IllegalArgumentException: Comparison method violates its general contract!,
在项目的对应代码中也发现了如下代码:
Collections.sort(aObjectList, new Comparator(){
public int compare(Object o1, Object o2)
{
......
int reverse = 1;
if ("desc".equalsIgnoreCase(sortType)) {
reverse = -reverse;
}
......
double result = filedValue1.doubleValue() - filedValue2.doubleValue();
int returnValue = 0;
if (result > 0.0D) {
returnValue = 1;
} else if (result < 0.0D) {
returnValue = -1;
}
return returnValue * reverse;
......
}
}
该代码也只会返回 -1 或 1,没有返回 0 的情况,如果两个相等的时候,在 JDK1.7 环境中运行就会出现上述异常了。
JDK1.6 Collections.sort(List, Comparator)方法的实现:
public static <T> void sort(List<T> list, Comparator<? super T> c) {
Object[] a = list.toArray();
Arrays.sort(a, (Comparator)c);
ListIterator i = list.listIterator();
for (int j=0; j<a.length; j++) {
i.next();
i.set(a[j]);
}
}
public static <T> void sort(T[] a, Comparator<? super T> c) {
T[] aux = (T[])a.clone();
if (c==null)
mergeSort(aux, a, 0, a.length, 0);
else
mergeSort(aux, a, 0, a.length, 0, c);
}
可以看到使用的是归并排序mertgeSort进行排序,
JDK1.7 Collections.sort(List, Comparator)方法的实现:
public static <T> void sort(List<T> list, Comparator<? super T> c) {
Object[] a = list.toArray();
Arrays.sort(a, (Comparator)c);
ListIterator i = list.listIterator();
for (int j=0; j<a.length; j++) {
i.next();
i.set(a[j]);
}
}
// Arrays.sort(a, c);
public static <T> void sort(T[] a, Comparator<? super T> c) {
if(LegacyMergeSort.userRequested){
legacyMergeSort(a,c)
}else{
TimSort.sort(a,c)
}
|
public static <T> void legacyMergeSort(T[] a, Comparator<? super T> c) {
T[] aux = (T[])a.clone();
if (c==null)
mergeSort(aux, a, 0, a.length, 0);
else
mergeSort(aux, a, 0, a.length, 0, c);
}
可以看到它会用 TimSort.sort()方法进行排序,且在 JDK1.7后默认的排序方法。
解决方法
既然在 sort()方法中使用 LegacyMergeSort.userRequested 参数来控制使用 mergeSort排序算法还是使用 TimSort 排序算法,那么只要 LegacyMergeSort.userRequested 为true就会使用mergeSort算法,就会兼容 JDK1.6 的Collections.sort() 方法了,好在 JVM 提供了一个参数来控制:
-Djava.util.Arrays.useLegacyMergeSort=true
所在就在出问题的那台服务器上的虚拟机中添加了该参数,再进行排序的时候,就可以正常排序了,不会再抛出该异常了。
接下来在看一下 LegacyMergeSort.userRequested 是怎么获取的:
static final class LegacyMergeSort {
private static final boolean userRequested =
java.security.AccessController.doPrivileged(
new sun.security.action.GetBooleanAction(
"java.util.Arrays.useLegacyMergeSort")).booleanValue();
}
可以看到也是根据 java.util.Arrays.useLegacyMergeSort 的值来控制的,所以按理说也可以通过设置该环境变量也实现同样的效果,但是我通过该方法并没有成功,最终在用添加虚拟机参数的方式进行解决。
System.setProperty("java.util.Arrays.useLegacyMergeSort", true)
以上两种方法只能是用来规避异常,让代码正常运行,是一种规避手段,而正确的做法应该是在写代码的过程中,在使用Collections.sort(List, Comparator)来排序的时候,尽量考虑周全,不要漏掉 0 的情况:
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (flag)
{
return -1;
}
else if(flag)
{
return 1;
}
else
{
return 0;
}
}
});
或者直接使用 compareTo 方法进行比较:
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
题外话:TimSort.sort
在 JDK 的源码中 Collections.sort(List, Comparator)方法底层就使用的是归并算法来进行排序的,而在 JDK1.7后,就会使用 TimSort 来进行排序了,而TimSort是归并排序的一种变种,在大部分情况下,TimSort算法的效率较高,且它是稳定,因为在大部分情况下,要排序的集合都是部分有序的,完全无序的集合很少出现,TimSort 就抓住这个规律而对归并排序进行优化的。
TimSort.sort的基本思想:
在要排序的集合中,把部分有序的那些元素找出来,这些部分有序的元素姑且称为一个区域,最终这个集合会被划分为若干个区域,之后把这些区域压入栈中,在栈中对这些区域按照一定的规则进行合并。
具体参考:维基百科