bigywater.tech
Java 内部原理 · 25

Arrays.sort 的稳定性:真实验证 Object[] 排序会保留相同分数的原始顺序

"Arrays.sort 对象数组是稳定排序,基本类型数组不是"是常见知识点,但"稳定"到底体现在哪、能不能真的拿来做二次排序,真实跑一遍最直观。

Alice Carol Eve
6 个学生按姓名字母序插入,只按分数降序排序后,相同分数(90 分)那组内部顺序真实还是插入时的字母序
int[] 没有身份
对照组:同样的分数放进 int[] 排序,排序结果里重复的 90 和 85 真实没有任何办法区分"这个是谁的"

上一篇:finally 里的 return 真实会吞掉异常:真实复现一个安静的 bug

思路:用姓名字母序当"隐藏的次要排序键"

先按一个无关紧要的顺序(姓名字母序)插入数据,只按另一个字段(分数)排序,如果排序算法稳定,相同分数的人应该还保持插入时的字母序。

SortStability.java(测试数据) Student[] students = { new Student("Alice", 90), new Student("Bob", 85), new Student("Carol", 90), new Student("Dave", 85), new Student("Eve", 90), new Student("Frank", 85), }; Arrays.sort(students, Comparator.comparingInt((Student s) -> s.score).reversed());

真实验证:相同分数组内,字母序真实被保留了下来

排序时的 Comparator 只比较 score 这一个字段,完全没有提到姓名——如果姓名顺序还能保留下来,只能是排序算法本身稳定的结果。

真实实测 · 只按 score 降序排序
原始顺序(按姓名字母序插入) = [Alice:90, Bob:85, Carol:90, Dave:85, Eve:90, Frank:85]

只按 score 降序排序后 = [Alice:90, Carol:90, Eve:90, Bob:85, Dave:85, Frank:85]

score=90 组内真实顺序 = Alice Carol Eve (插入时是 Alice Carol Eve,如果稳定应该保持这个顺序)
score=85 组内真实顺序 = Bob Dave Frank (插入时是 Bob Dave Frank,如果稳定应该保持这个顺序)
两组的内部顺序都真实和插入顺序完全一致,证明 Arrays.sort(Object[]) 底层的 TimSort 真实是稳定排序

这个特性在实际场景里很有用:想按"分数降序,分数相同的按姓名升序"这种多关键字排序,不需要一次性写一个比较两个字段的复杂 Comparator——只要先按姓名把数据排好(或者本来数据源就是按姓名排的),再单独按分数排一次,稳定排序会自动帮你把姓名顺序保留在相同分数的组内。

对照真实验证:int[] 排序,重复值没有身份可言

"稳定性"这个概念本身要求元素带有除排序键之外的额外身份信息——原始类型数组恰恰没有这种信息。

真实实测 · int[] 排序
排序前的分数: [90, 85, 90, 85, 90, 85]
排序后 = [85, 85, 85, 90, 90, 90]
3 个 90 和 3 个 85,但没人能说出"哪个 90 是 Alice 的、哪个是 Carol 的",因为 int[] 里从头到尾就没存过这个信息
所以 int[] 排序算"不稳定"吗?严格说,这个问题本身问得不太对——"稳定"描述的是"排序前后,相等元素的相对顺序是否保留",但 int 值本身就是它的全部信息,两个值相等的 int 在排序前后根本无法区分谁是谁,所以"稳定性"这个概念对纯粹的原始类型数组排序没有实际意义。JDK 文档里说 Arrays.sort(int[]) 用的是双轴快速排序、"不保证稳定",更准确的理解是"这个概念在这里不适用,所以 JDK 选了一个通常更快、但不满足稳定性的算法",而不是"它本来能稳定却选择不稳定"。

分步演示:"稳定排序"这四个字到底在保证什么

每一行都是真实运行这段代码得到的结果。

对象排序(TimSort)真实保证稳定性,基本类型数组排序(双轴快排)真实不保证——稳定性正是多级排序技巧的真实基础
这一步新验证的排序

    参考与说明

    • 本文全部排序结果均在本机真实运行 Java 17 得到,Arrays.sort(Object[])/Collections.sort 底层都是 TimSort,行为一致。
    • 和HashMap 可变 key 那篇一样,都是"对象身份 vs 值相等"这个话题下的真实验证。
    • 没有涉及:TimSort 内部合并相邻有序段(run)的具体算法细节、双轴快速排序对基本类型数组的分区策略——这些留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号