JDK1.6和JDK1.7中,Collections.sort的区别,

背景

最近,项目正在集成测试阶段,项目在服务器上运行了一段时间,点击表格的列进行排序的时候,有的列排序正常,有的列在排序的时候,在后台会抛出如下异常,查询到不到数据,而且在另外一台服务器上是可以排序的, 就是说该问题是偶现的;

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的基本思想:

在要排序的集合中,把部分有序的那些元素找出来,这些部分有序的元素姑且称为一个区域,最终这个集合会被划分为若干个区域,之后把这些区域压入栈中,在栈中对这些区域按照一定的规则进行合并。

具体参考:维基百科

 

转载于:https://my.oschina.net/mengyuankan/blog/1834497

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值