You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

maven-artifact的ComparableVersion与Timsort冲突问题及解决方案咨询

使用maven-artifact的ComparableVersion排序版本时触发异常的问题

问题背景

在项目中使用maven-artifact提供的ComparableVersion对制品版本进行排序,待排序版本以//分隔。

待排序版本列表

0.0.1576817712//0.0.3//0.0.4//0.0.4.//0.0.4.1//0.0.4.2//0.0.4.3//0.0.4.4//0.0.4.5//0.0.5//0.0.5.1//0.0.5.2//0.0.5.3//0.0.5.4//0.0.6//0.0.7//0.0.8//0.1.0//0.1.2//0.1.2.1//0.1.2.2//0.1.2.3//0.1.2.4//0.1.2.5//0.1.2.6//0.1.2.7//0.1.2.8//0.1.3//0.1.3.1//0.1.3.2//0.1.3.3//0.1.4//0.1.4.1//0.1.4.2//0.1.4.3//0.1.4.4//0.1.4.5//0.1.4.6//0.1.4.7//0.1.4.8//0.1.4.9//0.1.5//0.1.7//0.1.7.1//0.1.7.2//0.1.7.3//0.1.7.4//0.1.7.5//0.1.7.6//0.1.7.7//0.1.8//0.1.9//0.1.9.1//0.1.9.11//0.1.9.12//0.1.9.13//0.1.9.2//0.1.9.3//0.1.9.8//0.1.9.9//0.1.9.9.1//1.0.0.1//1.0.0.2//1.0.0.3//1.0.0.3.1//1.0.0.4//1.0.0.5//0.0.0.1-SNAPSHOT//0.0.11-SNAPSHOT//0.0.1576066498-SNAPSHOT//0.0.1576066912-SNAPSHOT//0.0.1576209616-SNAPSHOT//0.0.1576677646-SNAPSHOT//0.0.1576722159-SNAPSHOT//0.0.1576732580-SNAPSHOT//0.0.1576737990-SNAPSHOT//0.0.1576757185-SNAPSHOT//0.0.1576812388-SNAPSHOT//0.0.1576817712-SNAPSHOT//0.0.1576821661-SNAPSHOT//0.0.1576821977-SNAPSHOT//0.0.1576825998-SNAPSHOT//0.0.1577182101-SNAPSHOT//0.0.1577266235-SNAPSHOT//0.0.1577267400-SNAPSHOT//0.0.1577268933-SNAPSHOT//0.0.2-SNAPSHOT//0.0.3-SNAPSHOT//0.0.4-SNAPSHOT//0.0.4.1-SNAPSHOT//0.0.4.2-SNAPSHOT//0.0.4.4-SNAPSHOT//0.0.4.5-SNAPSHOT//0.0.5-SNAPSHOT//0.0.5.11151113-SNAPSHOT//0.0.5.2-SNAPSHOT//0.0.5.2.1-SNAPSHOT//0.0.5.20180829-SNAPSHOT//0.0.5.20181115-SNAPSHOT//0.0.5.4-SNAPSHOT//0.0.5.4.181113-SNAPSHOT//0.0.5.5-SNAPSHOT//0.0.6-SNAPSHOT//0.0.7-SNAPSHOT//0.0.8-SNAPSHOT//0.0.9-SNAPSHOT//0.0.91576063257-SNAPSHOT//0.0.91576066026-SNAPSHOT//0.1.0-SNAPSHOT//0.1.1-SNAPSHOT//0.1.2-SNAPSHOT//0.1.2.2-SNAPSHOT//0.1.2.3-SNAPSHOT//0.1.2.4-SNAPSHOT//0.1.2.5-SNAPSHOT//0.1.2.6-SNAPSHOT//0.1.2.7-SNAPSHOT//0.1.3-SNAPSHOT//0.1.3.1-SNAPSHOT//0.1.3.3-SNAPSHOT//0.1.3.4-SNAPSHOT//0.1.4-SNAPSHOT//0.1.4.1-SNAPSHOT//0.1.4.4-SNAPSHOT//0.1.4.5-SNAPSHOT//0.1.4.6-SNAPSHOT//0.1.4.7-SNAPSHOT//0.1.4.9-SNAPSHOT//0.1.4.l-SNAPSHOT//0.1.4.y-SNAPSHOT//0.1.4.yl-SNAPSHOT//0.1.4.yy-SNAPSHOT//0.1.5-push-SNAPSHOT//0.1.5-SNAPSHOT//0.1.5.1-SNAPSHOT//0.1.5.2-SNAPSHOT//0.1.5.3-SNAPSHOT//0.1.5.4-SNAPSHOT//0.1.5.5-SNAPSHOT//0.1.5.6-SNAPSHOT//0.1.5.l-SNAPSHOT//0.1.5.testpush-SNAPSHOT//0.1.5.y-SNAPSHOT//0.1.5.yl-SNAPSHOT//0.1.6-SNAPSHOT//0.1.7-SNAPSHOT//0.1.7.1-SNAPSHOT//0.1.7.2-SNAPSHOT//0.1.7.3-SNAPSHOT//0.1.7.4-SNAPSHOT//0.1.7.6.1-SNAPSHOT//0.1.8-SNAPSHOT//0.1.9.15-SNAPSHOT//0.1.9.16-SNAPSHOT//0.1.9.2-SNAPSHOT//0.1.9.6-SNAPSHOT//0.1.9.7-SNAPSHOT//0.1.9.9.2-SNAPSHOT//0.1.9.9.3-SNAPSHOT//0.2.0-SNAPSHOT//1.0.0.0-SNAPSHOT//1.0.0.1-SNAPSHOT//1.0.0.2-SNAPSHOT//1.0.0.3-lv-SNAPSHOT//1.0.0.3-SNAPSHOT//1.0.0.4-SNAPSHOT//1.0.0.5-SNAPSHOT//1.0.0.6-SNAPSHOT//1.0.0.7-SNAPSHOT//1.0.0.8-SNAPSHOT//1.0.0.9-SNAPSHOT//1.0.1-SNAPSHOT//1.0.1.0-SNAPSHOT//1.0.1.1-SNAPSHOT//1.0.1.2-SNAPSHOT//1.0.1.3-SNAPSHOT//

代码实现

String data = "...."; // 上述版本字符串
String[] versions = data.split("//");
List<ComparableVersion> comparableVersions = Arrays.stream(versions).map(ComparableVersion::new).collect(Collectors.toList());
Collections.sort(comparableVersions);

触发的异常

java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.util.ComparableTimSort.mergeHi(ComparableTimSort.java:866)
    at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:483)
    at java.util.ComparableTimSort.mergeForceCollapse(ComparableTimSort.java:422)
    at java.util.ComparableTimSort.sort(ComparableTimSort.java:222)
    at java.util.Arrays.sort(Arrays.java:1312)
    at java.util.Arrays.sort(Arrays.java:1506)
    at java.util.ArrayList.sort(ArrayList.java:1464)
    at java.util.Collections.sort(Collections.java:143)

已尝试的操作及疑问

  • 更换maven-artifact版本(3.8.6、3.8.7、3.6.3),问题依旧。
  • 通过JVM参数-Djava.util.Arrays.useLegacyMergeSort=true启用LegacyMergeSort可解决,但不想影响项目中所有排序逻辑。

疑问:

  1. ComparableVersion与Timsort冲突的原因是什么?
  2. 还有哪些针对性的排序解决方案?

问题解答

一、冲突原因

Timsort(Java 7及以上默认排序算法)严格遵守比较器的三大契约:

  1. 自反性:a.compareTo(a)必须返回0;
  2. 对称性:若a.compareTo(b) > 0,则b.compareTo(a) < 0;
  3. 传递性:若a.compareTo(b) > 0且b.compareTo(c) > 0,则a.compareTo(c) > 0。

而你的版本列表里的特殊格式版本,导致ComparableVersion的比较逻辑违反了这些契约:

  • 末尾带点的版本:比如0.0.4.,解析后本该和0.0.4等价,但部分场景下比较结果不一致,破坏对称性;
  • 含非数字后缀的版本:比如0.1.4.l-SNAPSHOT、0.1.5.testpush-SNAPSHOT,这类版本的字符串比较逻辑存在漏洞,导致传递性失效;
  • 不规范快照版本:部分快照版本的格式不符合Maven规范,使得ComparableVersion无法稳定判断优先级,进而触发契约违规。

LegacyMergeSort(旧版归并排序)对比较器契约的检查不严格,即使违反规则也能完成排序,所以启用后问题消失。

二、针对性解决方案

1. 预处理版本号(最优解)

先清理不规范的版本格式,从源头避免契约问题:

  • 去除版本末尾的点:比如把0.0.4.替换成0.0.4;
  • 标准化非数字后缀:要么把0.1.4.l-SNAPSHOT这类格式统一调整为合法格式,要么根据业务规则标记为无效版本并排除;
  • 确保快照版本符合Maven规范(比如x.y.z-SNAPSHOT,不要加多余后缀)。

预处理代码示例:

String cleanedVersion = version.replaceAll("\\.$", "") // 移除末尾的点
                               .replaceAll("[^0-9a-zA-Z.-]", ""); // 过滤非法字符(可根据业务调整)

2. 自定义比较器修复契约问题

用自定义比较器包裹ComparableVersion,处理特殊场景,确保比较结果符合契约:

Collections.sort(comparableVersions, (v1, v2) -> {
    // 先执行原有比较逻辑
    int result = v1.compareTo(v2);
    // 处理末尾带点的版本,强制判定为相等
    String v1Clean = v1.toString().replaceAll("\\.$", "");
    String v2Clean = v2.toString().replaceAll("\\.$", "");
    if (v1Clean.equals(v2Clean)) {
        return 0;
    }
    // 处理非数字后缀版本:将非规范版本排在规范版本之后(可根据业务调整规则)
    boolean v1HasInvalidSuffix = v1.toString().matches(".*[^0-9.-].*");
    boolean v2HasInvalidSuffix = v2.toString().matches(".*[^0-9.-].*");
    if (v1HasInvalidSuffix && !v2HasInvalidSuffix) {
        return 1;
    } else if (!v1HasInvalidSuffix && v2HasInvalidSuffix) {
        return -1;
    }
    return result;
});

3. 单独使用LegacyMergeSort

不修改全局JVM参数,手动封装旧版归并排序工具类,仅用于版本排序:

public class LegacyVersionSortUtil {
    public static <T extends Comparable<? super T>> void sort(List<T> list) {
        Object[] arr = list.toArray();
        legacyMergeSort(arr);
        ListIterator<T> iterator = list.listIterator();
        for (Object obj : arr) {
            iterator.next();
            iterator.set((T) obj);
        }
    }

    private static void legacyMergeSort(Object[] arr) {
        Object[] aux = arr.clone();
        mergeSort(aux, arr, 0, arr.length, 0);
    }

    private static void mergeSort(Object[] src, Object[] dest, int low, int high, int off) {
        int length = high - low;
        if (length < 7) {
            for (int i = low; i < high; i++) {
                for (int j = i; j > low && ((Comparable) dest[j-1]).compareTo(dest[j]) > 0; j--) {
                    swap(dest, j, j-1);
                }
            }
            return;
        }
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, off);
        mergeSort(dest, src, mid, high, off);
        if (((Comparable) src[mid-1]).compareTo(src[mid]) <= 0) {
            System.arraycopy(src, low, dest, low, length);
            return;
        }
        for (int i = low, p = low, q = mid; i < high; i++) {
            if (q >= high || (p < mid && ((Comparable) src[p]).compareTo(src[q]) <= 0)) {
                dest[i] = src[p++];
            } else {
                dest[i] = src[q++];
            }
        }
    }

    private static void swap(Object[] arr, int a, int b) {
        Object temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}

使用时直接调用:

LegacyVersionSortUtil.sort(comparableVersions);

4. 替换版本比较库

改用其他成熟的版本号解析库,比如semver4j或gradle-version,这些库的比较逻辑更严谨,能处理更多不规范格式:

// 示例:使用semver4j
List<Semver> semverList = Arrays.stream(versions)
                                .map(Semver::new)
                                .collect(Collectors.toList());
Collections.sort(semverList);

内容的提问来源于stack exchange,提问作者HHHHNNNNNNIIIII

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 22:45:34