Why does this program using Collections.sort only fail for lists of size 32 or more?

后端 未结 2 1579
攒了一身酷
攒了一身酷 2020-12-11 00:48

The following program throws the following exception:

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

相关标签:
2条回答
  • 2020-12-11 01:33

    It depends on the implementation, but in openjdk 8 the size of the array is checked against MIN_MERGE, which is equal to 32. This avoids the call to mergeLo/mergeHi which throw the exception.

    From 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;
    }
    
    0 讨论(0)
  • 2020-12-11 01:42

    Java 8 uses TimSort algorithm to sort the input. In TimSort there is a merging phase that happens when the length is at least 32. When the length is lower than 32 then a simple sorting algorithm is used that probably doesn't detect the contract violation. Let the source code comments of TimSort.java speak for itself:

    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;
    
    0 讨论(0)
提交回复
热议问题