当前位置: 首页 > 知识库问答 >
问题:

为什么使用Collections.sort的程序只对大小为32或更大的列表失败?

龚博涛
2023-03-14

以下程序引发以下异常:

java.lang.IllegalArgumentException: Comparison method violates its general contract!

我理解比较器的问题。请参阅无法复制:“比较方法违反了其总合同!

我不明白为什么它只对大小为32或更大的Lists失败。有人能解释吗?

class Experiment {

    private static final class MyInteger {
        private final Integer num;

        MyInteger(Integer num) {
            this.num = num;
        }
    }

    private static final Comparator<MyInteger> COMPARATOR = (r1, r2) -> {
        if (r1.num == null || r2.num == null)
            return 0;
        return Integer.compare(r1.num, r2.num);
    };

    public static void main(String[] args) {
        MyInteger[] array = {new MyInteger(0), new MyInteger(1), new MyInteger(null)};
        Random random = new Random();
        for (int length = 0;; length++) {
            for (int attempt = 0; attempt < 100000; attempt++) {
                List<MyInteger> list = new ArrayList<>();
                int[] arr = new int[length];
                for (int k = 0; k < length; k++) {
                    int rand = random.nextInt(3);
                    arr[k] = rand;
                    list.add(array[rand]);
                }
                try {
                    Collections.sort(list, COMPARATOR);
                } catch (Exception e) {
                    System.out.println(arr.length + " " + Arrays.toString(arr));
                    return;
                }
            }
        }
    }
}

共有2个答案

董高洁
2023-03-14

Java8使用TimSort算法对输入进行排序。在TimSort中,当长度至少为32时,会发生合并阶段。当长度小于32时,则使用简单的排序算法,可能不会检测到违反合同。让TimSort.java的源代码注释不言自明:

class TimSort<T> {
    /**
     * This is the minimum sized sequence that will be merged.  Shorter
     * sequences will be lengthened by calling binarySort.  If the entire
     * array is less than this length, no merges will be performed.
     *
     * This constant should be a power of two.  It was 64 in Tim Peter's C
     * implementation, but 32 was empirically determined to work better in
     * this implementation.  In the unlikely event that you set this constant
     * to be a number that's not a power of two, you'll need to change the
     * {@link #minRunLength} computation.
     *
     * If you decrease this constant, you must change the stackLen
     * computation in the TimSort constructor, or you risk an
     * ArrayOutOfBounds exception.  See listsort.txt for a discussion
     * of the minimum stack length required as a function of the length
     * of the array being sorted and the minimum merge sequence length.
     */
    private static final int MIN_MERGE = 32;
子车峰
2023-03-14

这取决于实现,但在openjdk 8中,数组的大小是根据MIN_MERGE检查的,MIN_MERGE等于32。这避免了对引发异常的mergeLo/mergeHi的调用。

从JDK/jdk/openjdk/7u40-b43 8-b132 7-b147-8-b132/java.util.TimSort:

static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
                     T[] work, int workBase, int workLen) {
    assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

    int nRemaining  = hi - lo;
    if (nRemaining < 2)
        return;  // Arrays of size 0 and 1 are always sorted

    // If array is small, do a "mini-TimSort" with no merges
    if (nRemaining < MIN_MERGE) {
        int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
        binarySort(a, lo, hi, lo + initRunLen, c);
        return;
    }

    /**
     * March over the array once, left to right, finding natural runs,
     * extending short natural runs to minRun elements, and merging runs
     * to maintain stack invariant.
     */
    TimSort<T> ts = new TimSort<>(a, c, work, workBase, workLen);
    int minRun = minRunLength(nRemaining);
    do {
        // Identify next run
        int runLen = countRunAndMakeAscending(a, lo, hi, c);

        // If run is short, extend to min(minRun, nRemaining)
        if (runLen < minRun) {
            int force = nRemaining <= minRun ? nRemaining : minRun;
            binarySort(a, lo, lo + force, lo + runLen, c);
            runLen = force;
        }

        // Push run onto pending-run stack, and maybe merge
        ts.pushRun(lo, runLen);
        ts.mergeCollapse();

        // Advance to find next run
        lo += runLen;
        nRemaining -= runLen;
    } while (nRemaining != 0);

    // Merge all remaining runs to complete sort
    assert lo == hi;
    ts.mergeForceCollapse();
    assert ts.stackSize == 1;
}
 类似资料:
  • 问题内容: 以下程序引发以下异常: 我了解的问题。请参阅无法复制:“比较方法违反了它的一般约定!” 我不明白为什么它只会在大小为32或更大的情况下失败。谁能解释? 问题答案: 它取决于实现,但是在openjdk 8 中,将根据MIN_MERGE(等于32)检查数组的大小。这避免了对/ 的调用,后者会引发异常。 从JDK / jdk / openjdk / 7u40-b43 8-b132 7-b14

  • 问题内容: 在应使用相同的列表时,我看到一些不一致之处。(Python 2.7.5) 有人有一个简单的解释吗? 问题答案: 使用列表文字,VM会创建具有设置长度的列表。当将序列传递给构造函数时,元素将被一个接一个地添加(通过),因此在适当时调整了列表的大小。由于调整大小操作是为了分摊成本而进行的,因此最终列表通常会比源列表大。

  • 为什么只适用于s而不适用于s?有什么特别的原因吗?

  • 问题内容: 我正在学习Java 8文档。我知道最大数组大小定义为均值2 ^ 31 – 8 = 2147483639 。然后,我集中讨论了为什么要减去8 或减去? 有些人根据文档给出了一些逻辑。因此,对于标题字,减去8。但是在这种情况下,如果标题字需要大于8,那么答案是什么? 请在此基础上澄清我。预先感谢您的合作。 问题答案: 阅读上述有关Java内存管理的文章,其中清楚指出 我认为这适用于Arra

  • 问题内容: 限制Java JVM上Permgen空间大小的目的是什么?为什么不总是将其设置为等于最大堆大小?Java为什么默认为这么少的64MB?他们是否正在试图通过这种方式迫使人们注意代码中的Permgen问题? 如果我的应用使用85MB的permgen,那么将其设置为96MB可能是安全的,但是如果它只是主堆的一部分,为什么还要设置得如此之小呢?允许JVM使用堆允许的PermGen效率不高吗?

  • 我为我的Java web应用程序分配了一个最大值。由于一些内存泄漏,应用程序已经消耗了将近2 GB的分配内存。此时,我已经使用进行了内存转储。在一个实例中,堆转储大小接近>1.5GB,而在另一个实例中,堆转储大小<100 MB。这背后的原因是什么?