Collections.sort引发的线上问题及原理研究

为何Collections.sort()会报IllegalArgumentException异常 [个人博客链接:这里的排版会更好~](https://www.hxxian.cn/blog/article.html?articleId=12) 案发现场 在Java开发中,我们对集合List进行排序,常会使用Collections.sort()方法进行排序,或者对于数组,使用Arrays.sort()方法进行排序。其实两者的sort()方法都是同一个,如下jdk源码: Collections#sort public static <T> void sort(List<T> . 阅读详情

目录

一、问题重现

二、Arrays.sort源码分析

三、TimSort

1、TimSort简介

2、TimSort原理

1)、二分插入排序

2)、minRunLength

3)、do-while循环

3、TimSort异常案例分析

四、总结

五、参考


一、问题重现

@Data
public class BusinessApplyDto implements Comparable<BusinessApplyDto>, Serializable {

    private static final long serialVersionUID = 7697030805482359129L;

    private String period;

    private Long businessId;

    @Override
    public int compareTo(BusinessApplyDto target) {
        String currentNo = this.period;
        String targetNo = target.getPeriod();
        if (currentNo == null) {
            return -1;
        } else if (targetNo == null) {
            return 1;
        }
        int sort = currentNo.compareTo(targetNo);
        if (sort == 0) {
            if (this.businessId == null) {
                return 1;
            } else if (target.businessId == null) {
                return -1;
            }
            return this.businessId.compareTo(target.businessId);
        }
        return -sort;
    }
}

        先仔细看下上面的代码,BusinessApplyDto实现了一个Comparable,然后重写了compareTo方法,compareTo方法通过比较两个字段的值来确定两个BusinessApplyDto的大小。然而compareTo方法过于复杂,初看代码不容易发现有bug,甚至上线后运行一段时间也不会有问题,因为如果没有特定的数据触发bug,线上一直都能正常运行。但是如果用下面数据进行运行,则会发生异常。

public class MainTest {

    public static void main(String[] args) {
        List<BusinessApplyDto> list = new ArrayList<>();
        BusinessApplyDto businessApplyDto1 = new BusinessApplyDto();
        businessApplyDto1.setPeriod("20210927");
        businessApplyDto1.setBusinessId(5L);
        list.add(businessApplyDto1);

        BusinessApplyDto businessApplyDto2 = new BusinessApplyDto();
        businessApplyDto2.setPeriod("20210927");
        list.add(businessApplyDto2);

        BusinessApplyDto businessApplyDto3 = new BusinessApplyDto();
        businessApplyDto3.setPeriod("20210927");
        list.add(businessApplyDto3);

        BusinessApplyDto businessApplyDto4 = new BusinessApplyDto();
        businessApplyDto4.setPeriod("20210927");
        list.add(businessApplyDto4);

        BusinessApplyDto businessApplyDto5 = new BusinessApplyDto();
        businessApplyDto5.setPeriod("20210927");
        list.add(businessApplyDto5);

        BusinessApplyDto businessApplyDto6 = new BusinessApplyDto();
        businessApplyDto6.setPeriod("20210927");
        list.add(businessApplyDto6);

        BusinessApplyDto businessApplyDto7 = new BusinessApplyDto();
        businessApplyDto7.setPeriod("20210927");
        list.add(businessApplyDto7);

        BusinessApplyDto businessApplyDto8 = new BusinessApplyDto();
        businessApplyDto8.setPeriod("20210927");
        businessApplyDto8.setBusinessId(2L);
        list.add(businessApplyDto8);

        BusinessApplyDto businessApplyDto9 = new BusinessApplyDto();
        businessApplyDto9.setPeriod("20210927");
        list.add(businessApplyDto9);

        BusinessApplyDto businessApplyDto10 = new BusinessApplyDto();
        businessApplyDto10.setPeriod("20210927");
        list.add(businessApplyDto10);

        BusinessApplyDto businessApplyDto11 = new BusinessApplyDto();
        businessApplyDto11.setPeriod("20210927");
        list.add(businessApplyDto11);

        BusinessApplyDto businessApplyDto12 = new BusinessApplyDto();
        businessApplyDto12.setPeriod("20210927");
        list.add(businessApplyDto12);

        BusinessApplyDto businessApplyDto13 = new BusinessApplyDto();
        businessApplyDto13.setPeriod("20210927");
        list.add(businessApplyDto13);

        BusinessApplyDto businessApplyDto14 = new BusinessApplyDto();
        businessApplyDto14.setPeriod("20210927");
        list.add(businessApplyDto14);

        BusinessApplyDto businessApplyDto15 = new BusinessApplyDto();
        businessApplyDto15.setPeriod("20210927");
        list.add(businessApplyDto15);

        BusinessApplyDto businessApplyDto16 = new BusinessApplyDto();
        businessApplyDto16.setPeriod("20210927");
        list.add(businessApplyDto16);

        BusinessApplyDto businessApplyDto17 = new BusinessApplyDto();
        businessApplyDto17.setPeriod("20210927");
        list.add(businessApplyDto17);

        BusinessApplyDto businessApplyDto18 = new BusinessApplyDto();
        businessApplyDto18.setPeriod("20210927");
        list.add(businessApplyDto18);

        BusinessApplyDto businessApplyDto19 = new BusinessApplyDto();
        businessApplyDto19.setPeriod("20210927");
        list.add(businessApplyDto19);

        BusinessApplyDto businessApplyDto20 = new BusinessApplyDto();
        businessApplyDto20.setPeriod("20210927");
        list.add(businessApplyDto20);

        BusinessApplyDto businessApplyDto21 = new BusinessApplyDto();
        businessApplyDto21.setPeriod("20210927");
        list.add(businessApplyDto21);

        BusinessApplyDto businessApplyDto22 = new BusinessApplyDto();
        businessApplyDto22.setPeriod("20210927");
        list.add(businessApplyDto22);

        BusinessApplyDto businessApplyDto23 = new BusinessApplyDto();
        businessApplyDto23.setPeriod("20210927");
        list.add(businessApplyDto23);

        BusinessApplyDto businessApplyDto24 = new BusinessApplyDto();
        businessApplyDto24.setPeriod("20210927");
        list.add(businessApplyDto24);

        BusinessApplyDto businessApplyDto25 = new BusinessApplyDto();
        businessApplyDto25.setPeriod("20210927");
        list.add(businessApplyDto25);

        BusinessApplyDto businessApplyDto26 = new BusinessApplyDto();
        businessApplyDto26.setPeriod("20210927");
        list.add(businessApplyDto26);

        BusinessApplyDto businessApplyDto27 = new BusinessApplyDto();
        businessApplyDto27.setPeriod("20210927");
        list.add(businessApplyDto27);

        BusinessApplyDto businessApplyDto28 = new BusinessApplyDto();
        businessApplyDto28.setPeriod("20210927");
        list.add(businessApplyDto28);

        BusinessApplyDto businessApplyDto29 = new BusinessApplyDto();
        businessApplyDto29.setPeriod("20210927");
        list.add(businessApplyDto29);

        BusinessApplyDto businessApplyDto30 = new BusinessApplyDto();
        businessApplyDto30.setPeriod("20210927");
        list.add(businessApplyDto30);

        BusinessApplyDto businessApplyDto31 = new BusinessApplyDto();
        businessApplyDto31.setPeriod("20210927");
        businessApplyDto31.setBusinessId(6L);
        list.add(businessApplyDto31);

        BusinessApplyDto businessApplyDto32 = new BusinessApplyDto();
        businessApplyDto32.setPeriod("20210927");
        list.add(businessApplyDto32);

        Collections.sort(list);
    }
}

        如果执行上述main方法,会报下面的异常:

Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract!
	at java.util.ComparableTimSort.mergeLo(ComparableTimSort.java:744)
	at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:481)
	at java.util.ComparableTimSort.mergeCollapse(ComparableTimSort.java:406)
	at java.util.ComparableTimSort.sort(ComparableTimSort.java:213)
	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)
	at MainTest.main(MainTest.java:148)

上述的MainTest数据中,如果businessId都是null不会报错,只有特殊数据才会触发这个错误。至于为何触发这个错误,比较直观的原因是BusinessApplyDto中的compareTo违反了sort的几个原则:

  1. 自反性:x,y 的比较结果和 y,x 的比较结果相反。
  2. 传递性:x>y,y>z,则 x>z。
  3. 对称性:x=y,则 x,z 比较结果和 y,z 比较结果相同。

下文深入sort源码,分析出现上述错误的根本原因。

二、Arrays.sort源码分析

        查看Collections.sort可以知道底层是调用了Arrays.sort方法,Arrays.sort的代码如下所示:

    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }

        通过上述代码可以发现,如果c为null则会调用sort(Object[] a)方法,否则会根据LegacyMergeSort.userRequested是否为true调用legacyMergeSort方法还是TimSort.sort方法。而LegacyMergeSort.userRequested可以通过在JVM参数中添加-Djava.util.Arrays.useLegacyMergeSort=true/false来指定。sort(Object[] a)代码如下所示:

    public static void sort(Object[] a) {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a);
        else
            ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
    }

        可见sort(Object[] a)和sort(T[] a, Comparator<? super T> c)方法大致逻辑相同,sort(Object[] a)底层调用了ComparableTimSort.sort,而sort(T[] a, Comparator<? super T> c)调用了TimSort.sort。其实ComparableTimSort和TimSort都是调用TimSort算法,只是ComparableTimSort通过Comparable比较,而TimSort是通过Comparator比较而已。legacyMergeSort方法上注释了/** To be removed in a future release. */,说明会在未来版本中去掉。接下来就不贴详细的代码了,整理了一张Arrays.sort的流程图。

         从上述的流程图可以感受到Arrays.sort底层实现是比较复杂的。通过流程图可以发现legacyMergeSort的底层逻辑会判断数组,如果数组大小小于7,则使用插入排序对数组进行排序,否则使用归并排序。ComparableTimSort和TimSort逻辑一致,都是使用TimSort实现的。而最开始报的Comparison method violates its general contract!错误也是在TimSort里抛出来的,因此下面主要分析下TimSort。

三、TimSort

1、TimSort简介

TimSort由Tim Peter于2002年发明的,是一个自适应的、混合的、稳定的排序算法,融合了归并算法和二分插入排序算法的精髓,在现实世界的数据中有着特别优秀的表现,因此被用作Java和Python的内置排序算法。这个算法之所以快,是因为它充分利用了现实世界的待排序数据里面,有很多子串是已经排好序的不需要再重新排序,利用这个特性并且加上合适的合并规则可以更加高效的排序剩下的待排序序列。

2、TimSort原理

TimSort结合了二分插入排序和归并排序。在数组长度小于一定阈值时使用二分插入排序,否则计算minRun再进行二分插入排序+归并排序。这个阈值在TimSort最初实现的时候是64,后来改成32。之所以改成32是因为这个阈值能得到更好的性能。

1)、二分插入排序

二分插入排序先通过二分搜索,直接找到这个元素应该插入的位置,就可以减少很多次的比较。虽然仍然是需要移动相同数量的元素,但是复制数组的时间消耗要小于元素间的一一互换。例如数组[3,7,12,32,50,1],如果想把1插入到数组最前面,通过直接插入排序需要经过5次比较,但是使用二分插入排序,只需要2次比较就能找到插入的位置,然后使用System.arraycopy对数组进行移动。

2)、minRunLength

minRunLength方法的主要作用就是确认run的最小值,确定minRun值后,待排序数组将被分成以minRun大小为区块的一块块子数组。minRunLength的代码如下所示:

    private static int minRunLength(int n) {
        assert n >= 0;
        int r = 0;      // Becomes 1 if any 1 bits are shifted off
        while (n >= MIN_MERGE) {
            r |= (n & 1);
            n >>= 1;
        }
        return n + r;
    }

        从代码中可以看出minRunLength的大小在0到32之间。n<100的时候n和minRunLength(n)的对应数据如下:

n    minRunLength(n)
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
10    10
11    11
12    12
13    13
14    14
15    15
16    16
17    17
18    18
19    19
20    20
21    21
22    22
23    23
24    24
25    25
26    26
27    27
28    28
29    29
30    30
31    31
32    16
33    17
34    17
35    18
36    18
37    19
38    19
39    20
40    20
41    21
42    21
43    22
44    22
45    23
46    23
47    24
48    24
49    25
50    25
51    26
52    26
53    27
54    27
55    28
56    28
57    29
58    29
59    30
60    30
61    31
62    31
63    32
64    16
65    17
66    17
67    17
68    17
69    18
70    18
71    18
72    18
73    19
74    19
75    19
76    19
77    20
78    20
79    20
80    20
81    21
82    21
83    21
84    21
85    22
86    22
87    22
88    22
89    23
90    23
91    23
92    23
93    24
94    24
95    24
96    24
97    25
98    25
99    25

3)、do-while循环

        计算出minRunLength之后,进入一个do-while循环,此循环主要的作用就是通过run和栈实现区块的排序。do-while里主要有这么几个步骤:

  1. countRunAndMakeAscending找到一个run,这个run必须是有序的,如果是降序的,则会对其进行升序操作。
  2. 步骤a确定的区块大小如果小于minRunLength值,则调用binarySort对run进行扩展。
  3. 调用ts.pushRun(lo, runLen)将当前run维护到栈中,为后面的mergeCollapse做准备。
  4. 调用ts.mergeCollapse()对每个区块进行归并。mergeCollapse会对不同的run区块进行merge,假设有三个连续的区块A、B、C。会遵从下述规则进行归并:
    1. 只会将A和B或者B和C进行合并,不会将A和C进行合并。
    2. 如果当前只有两个区块,则如果A<B,则将A和B进行归并。
    3. 如果当前有三个区块,如果A<=B+C,将A和B进行合并,直到满足A>B+C并且B>Z。
  5. 重复执行步骤a到步骤d,直到待排序数组全部排序完毕。
  6. 最后调用mergeForceCollapse归并所有runs,直到只有一个run为止。 
具体数据分析:

为了便于演示,将MIN_MERGE设置成2(源代码为32);将minRun设置为2。
假设初始数组为[9,6,3,4,5,12,14,15,7,11,1,8,13,16,18,19]
1、执行步骤a,调用countRunAndMakeAscending,计算出runLen的值。如果有连续降序的则进行升序操作。第一步操作结束后,数据变成[3,6,9][4,5,12,14,15,7,11,1,8,13,16,18,19]。
2、执行步骤c,将当前run维护到栈中,当前栈区块为[3]。
3、执行步骤d,调用ts.mergeCollapse(),但是由于stackSize=1,因此无需进入mergeCollapse的while循环。
4、继续执行步骤a,调用countRunAndMakeAscending。数据变成[3,6,9][4,5,12,14,15][7,11,1,8,13,16,18,19]。
5、执行步骤c,栈的数据为[3,5]。
6、执行步骤d,因为runLen[0]=3,runLen[1]=5,所以runLen[0]<=runLen[1]。于是进入mergeAt方法。
  6.1、调用gallopRight计算run1的第一个元素在run0中的位置,既run1中的4应该插在run0中的3后面。
  6.2、调用gallopLeft计算run0的最后一个元素在run1中的位置,9应该插在12之前,然后忽略run1此位置之后的所有元素,因为都比run0中的元素大。
  6.3、这样剩下的需要排序的元素就是[6,9][4,5]。然后调用mergeLo。完成之后的数据为:[3,4,5,6,9,12,14,15][7,11,1,8,13,16,18,19]。
7、执行步骤a,寻找连续升序或降序数据,结果数据变成[3,4,5,6,9,12,14,15][7,11][1,8,13,16,18,19]。
8、执行步骤c,当前栈区块为[8,2]。
9、执行步骤d,调用ts.mergeCollapse(),因为runLen[0]=8,runLen[1]=2,runLen[0]>runLen[1],因此不进入while循环。
10、执行步骤a,寻找连续升序或降序数据,结果为[3,4,5,6,9,12,14,15][7,11][1,8,13,16,18][19]。
11、执行步骤c,当前栈区块为[8,2,5]。
12、执行步骤d,runLen[0]=8,runLen[1]=2,runLen[2]=5,因为runLen[0]>runLen[1]+runLen[2],但是runLen[1]<=runLen[2],因此归并run0和run2。结果数据为[3,4,5,6,9,12,14,15][1,7,8,11,13,16,18][19]。
13、执行步骤c,当前栈区块为[8,7]。
14、执行步骤d,调用ts.mergeCollapse()。runLen[0]=8,runLen[1]=7,runLen[0]>runLen[1],因此不进入while循环。
15、执行步骤a、寻找连续升序或降序数据,结果为[3,4,5,6,9,12,14,15][1,7,8,11,13,16,18][19]。
16、执行步骤c,当前区块为[8,7,1]。
17、执行步骤d,调用ts.mergeCollapse(),runLen[0]=8,runLen[1]=7,runLen[2]=1,因为runLen[0]<=runLen[1]+runLen[2]并且runLen[0]>runLen[2],因此先将run1和run2进行合并。
  17.1、调用gallopRight计算run2的第一个元素在run1中的位置,既run2中的19应该插在run1中的18后面。
  17.2、调用gallopLeft计算run1的最后一个元素在run2中的位置,18应该插在19前面。
  17.3、剩下的元素为[19]。然后调用mergeHi。完成之后的数据为:[3,4,5,6,9,12,14,15][1,7,8,11,13,16,18,19]。当前入栈的区块为[8,8]。
18、继续while循环,因为runLen[0]=8,runLen[1]=8,runLen[0]<=runLen[1]。
  18.1、调用gallopRight,run1的1插在run0的3前面。
  18.2、调用gallopLeft,run0的15应该插在13前面。
  18.3、剩下待排序数组为[3,4,5,6,9,12,14,15][1,7,8,11,13]。然后调用mergeHi,结果数据为[1,3,4,5,6,7,8,9,11,12,13,14,15,16,18,19]。当前入栈区块大小为[16]。
19、最后调用mergeForceCollapse。由于stackSize=1,因此不要进入while循环,结束整个sort操作。

3、TimSort异常案例分析

        本文最开始MainTest中的那些数据中,其实period都是相等的,因此数据可以只看businessId字段,因此上面代码中可以抽象成数组:[5,null,null,null,null,null,null,2, null,null,null,null,null,null,null,null, null,null,null,null,null,null,null,null, null,null,null,null,null,null,6,null]。归并一次后的数据结果,将数组分成两组:[2,5,null,null,null,null,null,null, null,null,null,null,null,null,null,null][6,null,null,null,null,null,null,null, null,null,null,null,null,null,null,null]。其中runLen[0]=16,runLen[1]=16。在调用mergeCollapse后,由于runLen[n] <= runLen[n + 1],因此会进入mergeAt(n)方法。

        在进入mergeAt方法后,调用gallopRight后确定的值为2,因为run1中的6比run0中的5打,因此插入的索引值是2。而调用gallopLeft后确定的索引值为16。然后进入mergeLo方法,将[null,null,null,null,null,null, null,null,null,null,null,null,null,null]和[6,null,null,null,null,null,null,null, null,null,null,null,null,null,null,null]进行合并。合并后的结果是[2,5,6,null,null,null,null,null,null,null, null,null,null,null,null,null,null,null, null,null,null,null,null,null, null,null,null,null,null,null,null,null]。

        当继续执行时,发现len1的值最终减到0,于是进入下图逻辑中,于是就抛出了Comparison method violates its general contract!异常。

        通过分析可以知道,抛出这个异常的根本原因就是BusinessApplyDto的compareTo方法中违法了自反原则导致的。上述数组中的null代表businessId为null,因此在BusinessApplyDto中会返回1(见下面代码片段),代表升序。也就是说businessApplyDto2>=businessApplyDto3,但是在归并过程中又是businessApplyDto2<businessApplyDto3。违反了自反原则,所以最终导致抛出异常。

if (this.businessId == null) {
   return 1;
}

        找出问题原因后,就是需要修复bug,修复后的代码如下所示:

@Data
public class BusinessApplyDto implements Comparable<BusinessApplyDto>, Serializable {

    private static final long serialVersionUID = 7697030805482359129L;

    private String period;

    private Long businessId;

    @Override
    public int compareTo(BusinessApplyDto target) {
        String currentNo = this.period;
        String targetNo = target.getPeriod();
        if (currentNo == null) {
            return -1;
        } else if (targetNo == null) {
            return 1;
        }
        int sort = currentNo.compareTo(targetNo);
        if (sort == 0) {
            if (this.businessId == null 
                    && target.businessId == null) {
                return 0;
            }
            if (this.businessId == null) {
                return 1;
            } else if (target.businessId == null) {
                return -1;
            }
            return this.businessId.compareTo(target.businessId);
        }
        return -sort;
    }
}

四、总结

        本文最开始抛砖引玉展示出现错误的现象,然后深入源码进行研究,揭开TimSort的实现原理及其中隐藏的比较器准则:自反性、传递性、对称性,最后解决上述的bug。通过本文可以发现TimSort中比较容易遇到的坑,在实现Comparable的时候,一定要确保返回结果包含-1,0,1三种结果,否则就有可能违反TimSort准则。

五、参考

 世界上最快的排序算法——Timsort - 佛西先森 - 博客园

java.util.ComparableTimSort中的sort()方法简单分析_生命在于折腾-优快云博客

OpenJDK 源代码阅读之 TimSort_on_1y-优快云博客

深入jdk——追踪Collections.sort 引发的bug(1)mergeSort 上篇博客介绍了因为重写比较方法引发的bug,这篇博客,咱们深入以下jdk对Collections.sort的实现,看看这个异常产生的原因,废话不多,先看源码:1,Collections.sortpublic static void sort(List list, Comparator c) { Object[] a = list.toArray(); Arrays 阅读详情

相关推荐

深入jdk——追踪Collections.sort 引发的bug(2)TimSort思路

1. 为什么写这篇文章这篇文章的根源是在产品中发现了一个诡异的bug:只能在产品环境下重现,在我的本地开发环境无法重现,而双方的代码没有任何区别。最后用remotedebug的方法找到异常所在:Exception in thread "main"java.lang.IllegalArgumentException: Comparisonmethod violates its general con

ESOO 2212

Exception in thread “main“ java.lang.IllegalArgumentException:解决方案

Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract!解决方法

牧羊少年 4万+

Java Collections.sort()底层原理与高并发排序实战

排序是编程中最基础的算法操作之一,其核心涉及比较、交换与稳定性保障。Java中Collections.sort()作为最常用的集合排序工具,实际封装了TimSort、双轴快排与插入排序等多策略协同机制,依据数据规模、有序性特征及Comparator契约动态选择最优路径。它不仅关乎时间复杂度,更直接影响内存分配、GC压力与线程安全行为。在电商订单、日志分析、实时榜单等典型业务场景中,错误使用Comparator、忽略List实现类差异(如ArrayList vs LinkedList)、盲目在高并发下直接调用

weixin_34009794的博客 395

JDK1.7 Collections.sort Arrays.sort发现的问题

使用jdk1.7后发现的collections.sort(List list)排序问题和浅析          自己的电脑系统重装以后,装了一个最新的jdk(1.7),当然了,本地eclipse的编译等级仍然是1.5。 之后有一个功能是需要调用服务端的一个方法来达到对ArrayList排序的功能,大部分朋友应该都知道java.util.Collections下有一个sort(List&l...

asdfede的博客 710

Java Collections算法实战:排序、查找与高频应用场景解析

在Java编程中,集合框架是处理数据结构的核心基础。排序与查找作为算法中最基础的操作,其高效实现直接影响程序性能。Java Collections框架通过封装成熟的算法,为开发者提供了稳定、高效的解决方案。例如,Collections.sort()方法底层采用TimSort算法,这是一种自适应、稳定的混合排序算法,结合了归并排序和插入排序的优点,对部分有序数据表现优异。在工程实践中,这些封装好的算法方法能显著提升开发效率,避免重复造轮子。它们广泛应用于数据处理、面试算法题解、系统性能优化等场景。本文聚焦于C

weixin_30414305的博客 305

Java List排序实战:原理、性能与避坑指南

Java中的列表排序远不止调用sort方法那么简单,它涉及算法稳定性、时间复杂度、内存模型及线程安全等底层机制。Timsort作为默认稳定排序算法,在部分有序数据场景下表现优异;而双轴快排虽快却不稳定,需谨慎选用。Comparator的正确实现直接影响业务一致性,尤其在处理null值、BigDecimal精度、中文拼音排序等高频工程场景中极易引发线上故障。本文围绕ArrayList、LinkedList等常见List实现,结合JDK源码与百万级订单压测实践,系统解析Collections.sort、List

weixin_30399155的博客 307

深入分析集合List的排序Collections.sort

List接口本身未提供sort的方法。 在jdk中提供了一个集合操作工具类Collections来操作集合。 查看Collections类,可以发下如下两个方法: static Comparable> void sort(List list) 根据元素的自然顺序 对指定列表按升序进行排序。 static

3527

Python collections模块:高效数据结构实战指南

Python中的数据结构是程序性能与可维护性的基石。从基础字典的KeyError陷阱到列表头部插入的O(n)性能瓶颈,传统内置类型在复杂场景下常需大量防御性代码。collections模块通过namedtuple、defaultdict、Counter、deque等专用类型,将常见操作语义化、自动化和高性能化——前者提供不可变命名元组以消除位置索引歧义,后者实现惰性默认值与常数时间双端操作。这些组件不仅提升运行效率(实测defaultdict计数快1.78倍,deque头部插入快15倍),更显著降低认知负荷

weixin_30627341的博客 401

Collections.sort排序出错?

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 欢迎使用Ma...

qq_35926663的博客 1697

Java ArrayList排序全解析:从基础API到Java 8实战与性能优化

在Java开发中,集合排序是数据处理的核心基础操作,它直接关系到数据检索效率和业务逻辑的清晰度。其原理主要基于比较排序算法,Java标准库提供了稳定高效的TimSort实现。从技术价值看,掌握排序不仅能提升代码性能,更是理解对象比较、算法稳定性和API设计的重要实践。在实际应用场景中,无论是简单数据类型排序、自定义对象的多字段排序,还是处理包含空值的复杂数据集,都需要根据具体需求选择合适的策略。本文聚焦ArrayList排序,深入探讨了Collections.sort()与List.sort()的底层机制,

weixin_34062469的博客 375

Unity游戏开发中List.Sort()多条件排序:原理、实现与性能优化

在C#编程中,集合排序是数据处理的基础操作,其核心原理是通过比较算法确定元素间的相对顺序。List<T>.Sort()方法利用快速排序算法实现高效原地排序,避免了LINQ OrderBy可能产生的GC开销,这对于性能敏感的游戏开发尤为重要。通过自定义Comparison<T>委托或实现IComparer<T>接口,开发者可以灵活定义多属性排序规则,例如在角色扮演游戏中,按照攻击力、防御力等多维度属性决定单位行动顺序。这种技术方案不仅提升了代码的可维护性,还能有效优化运行时性能,是Unity游戏开发中处理复杂

weixin_30571465的博客 500

深入jdk——追踪Collections.sort 引发的bug(3)TimSort源码解读

本来准备看Java容器源码的。但是看到一开始发现Arrays这个类我不是很熟,就顺便把Arrays这个类给看了。Arrays类没有什么架构与难点,但Arrays涉及到的两个排序算法似乎很有意思。那顺便把TimSort算法和双指针快速排序也研究一下吧。首先强调一下,这是个稳定的排序算法看过代码之后觉得这个算法没有想象的那么难。逻辑很清晰,整个算法最大的特点就是充分利用数组中已经存在顺序。在归并的过程

ESOO 1856

为什么桥式传感器(Bridge Sensor)需要用到仪表放大器?

仪表放大器在桥式传感器的应用里起到关键作用,本文介绍桥式传感器的原理、特性,以及信号调理过程,并附上了一个 TINA - TI 的仿真电路。

疯狂的运放 1527

Java List排序方法与性能优化全解析

集合排序是Java开发中的基础操作,其核心在于比较逻辑的实现。Java通过Comparable和Comparator两种机制支持排序,前者定义自然顺序,后者提供灵活的比较规则。从数据结构角度看,List排序通常采用TimSort算法,时间复杂度为O(n log n)。在实际工程中,合理的排序实现能显著提升系统性能,特别是在电商商品排序、社交动态流等高频场景。通过预计算比较键、使用稳定排序等方法,可以避免常见的并发修改异常和内存泄漏问题。Java 8引入的Lambda表达式和Comparator组合方法,大幅

weixin_30855761的博客 430

OpenCV实战:用C++和Python实现亚像素边缘检测(附完整代码)

本文详细介绍了使用OpenCV实现亚像素边缘检测的技术,通过C++和Python双语言代码解析,提升工业测量和医学影像中的检测精度。文章涵盖算法原理、核心实现步骤及工业应用案例,帮助开发者掌握亚像素精度处理技术,解决高精度场景中的边缘检测难题。

beer8的博客 373

RISC-V— 架构基础知识学习

蜂鸟 E200 系列是一个开源的RISC-V 处理器。蜂鸟 E200 系列由中国大陆研发团队开发,用户能够轻松与开发人员取得交流和支持。RI蜂鸟 E200 处理器研发团队拥有在国际一流公司多年开发处理器的经验,使用稳健的Verilog 2001 语法编写的可综合 RTL 代码,以工业级标准进行开发。蜂鸟 E200 的代码为人工编写,添加丰富的注释且可读性强,非常易于理解。

deilt 1万+
上一篇: mapstruct常见用法梳理
下一篇: logback-spring.xml配置详解
datastructure18
博客等级 码龄19年 6粉丝 10原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值