1. 概述

排序是计算机科学的基础操作,在各类应用的数据组织和处理中至关重要。本文将深入对比 Java 中两种常用排序方法:Arrays.sort()Collections.sort()。虽然两者核心功能相同——对数据进行排序——但在实现机制、适用场景和性能表现上存在显著差异。

2. 基础对比

2.1. Arrays.sort()

Arrays.sort() 是 Java 数组操作的核心工具类方法:

  • 适用对象:所有数组类型(基本类型数组 & 对象数组)
  • 核心能力
    • 对基本类型(int/double等)进行升序排序
    • 对对象数组支持自定义比较器(Comparator)
  • ⚠️ 所属包java.util.Arrays
  • 📝 典型场景:处理固定大小的数据集,如配置数组、原始数据集合等

2.2. Collections.sort()

Collections.sort() 专为集合框架设计:

  • 适用对象:所有 List 接口实现类
  • 核心能力
    • 动态排序 ArrayList/LinkedList 等可变集合
    • 支持自然排序(Natural Ordering)和自定义比较器
  • ⚠️ 所属包java.util.Collections
  • 📝 典型场景:处理动态数据结构,如数据库查询结果、用户输入列表等

3. 稳定性对比

稳定性指相等元素的原始相对顺序在排序后是否保持不变。通过任务排序示例说明:

// 任务集合初始化
tasks = new ArrayList<>();
tasks.add(new Task(1, 1, "2023-09-01"));
tasks.add(new Task(2, 2, "2023-08-30"));
// ...(其他任务初始化)

3.1. 稳定排序(Collections.sort())

final List<Task> tasks = Tasks.supplier.get();
Collections.sort(tasks, Comparator.comparingInt(Task::getPriority)); // 先按优先级排序
Collections.sort(tasks, Comparator.comparing(Task::getDueDate));     // 再按截止日期排序

输出结果(优先级顺序在相同时保留):

Task: #9  | Priority: 1 | Due Date: 2023-08-28
Task: #7  | Priority: 3 | Due Date: 2023-08-28
Task: #3  | Priority: 1 | Due Date: 2023-08-29
Task: #12 | Priority: 1 | Due Date: 2023-08-30  // 优先级1保持在前
Task: #2  | Priority: 2 | Due Date: 2023-08-30  // 优先级2在后

3.2. 非稳定排序(自定义快速排序)

List<Task> tasks = Tasks.supplier.get();
quickSort(tasks, Comparator.comparingInt(Task::getPriority));
quickSort(tasks, Comparator.comparing(Task::getDueDate));

输出结果(优先级顺序被打乱):

Task: #9  | Priority: 1 | Due Date: 2023-08-28
Task: #7  | Priority: 3 | Due Date: 2023-08-28
Task: #3  | Priority: 1 | Due Date: 2023-08-29
Task: #2  | Priority: 2 | Due Date: 2023-08-30  // 优先级2意外在前
Task: #12 | Priority: 1 | Due Date: 2023-08-30  // 优先级1反而在后

3.3. 关键结论

  • 对象排序:必须使用稳定算法(如 TimSort/Merge Sort)
  • 基本类型:无需稳定性(值相同即完全相同)
  • 📌 实现差异
    • Arrays.sort() 对基本类型用非稳定算法(Dual-Pivot Quicksort)
    • 对象类型和 Collections.sort() 统一使用稳定算法(TimSort)

4. 复杂度深度剖析

4.1. 时间复杂度

通过 JMH 基准测试对比 Merge Sort 和 Quicksort:

@Benchmark
public void quickSortRandomNumber() {
    Quicksort.sort(randomNumbersSupplier.get());
}
@Benchmark
public void mergeSortRandomNumber() {
    MergeSort.sort(randomNumbersSupplier.get());
}

测试结果

Benchmark                                    Mode  Cnt     Score     Error  Units
PerformanceBenchmark.mergeSortRandomNumber  thrpt    4  1489.983 ± 401.330  ops/s
PerformanceBenchmark.quickSortRandomNumber  thrpt    4  2757.273 ±  29.606  ops/s

📊 关键发现

  • 随机数据:Quicksort 吞吐量 ≈ Merge Sort 的 2 倍(原地排序优势)
  • 极端数据(全相同元素):
    PerformanceBenchmark.mergeSortSameNumber    thrpt    4  5295.502 ± 98.624 ops/s
    PerformanceBenchmark.quickSortSameNumber    thrpt    4   118.211 ± 0.117 ops/s
    
    Quicksort 退化到 O(n²),Merge Sort 保持稳定

4.2. 空间复杂度

通过 GC 日志分析内存占用:

指标 MergeSort Quicksort
GC 触发次数 26,848 588
GC 暂停时间占比 0.72% 0.02%
平均 GC 间隔(ms) 46.6 2124

📌 核心结论

  • Quicksort:空间复杂度 O(log n),内存友好
  • MergeSort:需要 O(n) 额外空间,频繁触发 GC

4.3. 优化实践

对包装类型(Integer)的优化方案:

@Benchmark
public void sortingPrimitiveArray(Input input) {
    // 转基本类型排序
    final int[] array = input.randomNumbers.get()
        .stream()
        .mapToInt(Integer::intValue)
        .toArray();
    Arrays.sort(array);
    // 转回对象
    final List<Integer> result = Arrays.stream(array)
        .boxed()
        .collect(Collectors.toList());
}

性能对比

Benchmark                                       Mode  Cnt     Score     Error  Units
ObjectOverheadBenchmark.sortingPrimitiveArray  thrpt    4  1998.818 ± 373.654 ops/s  // ✅ 最优
ObjectOverheadBenchmark.sortingObjectArray     thrpt    4   982.849 ±  19.201 ops/s
ObjectOverheadBenchmark.sortingObjects         thrpt    4   976.778 ±  10.580 ops/s

💡 优化技巧:对包装类型集合,可临时转为基本类型数组排序,性能提升超 100%

5. 实现机制深度解析

5.1. Arrays.sort() 的算法选择

// 伪代码实现逻辑
if (数组是基本类型) {
    使用 Dual-Pivot Quicksort  // 非稳定,空间效率高
} else {
    使用 TimSort              // 稳定,适合对象
}

5.2. Collections.sort() 的实现本质

// 实际调用 Arrays.sort()
public static <T> void sort(List<T> list) {
    list.sort(null);  // 委托给 List 实现
}

// ArrayList 的实现
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);  // 调用 Arrays.sort()
    // ... 重新填充数组
}

📌 关键洞察

  • Collections.sort() 本质是 Arrays.sort() 的封装
  • 最终都依赖 Arrays.sort() 的实现逻辑
  • 算法选择取决于数据类型(基本类型 vs 对象)

6. 总结

6.1. 核心差异速查表

特性 Arrays.sort() Collections.sort()
适用对象 数组(基本/对象) List 实现类
稳定性 基本类型非稳定 始终稳定
核心算法 Dual-Pivot Quicksort TimSort
空间复杂度 O(log n) O(n)
最佳场景 原始数据/固定大小数组 动态集合/对象排序

6.2. 实践建议

  • 优先使用场景
    • 处理基本类型数组 → Arrays.sort()
    • 需要保持对象顺序 → Collections.sort()
  • ⚠️ 踩坑预警
    • 对基本类型数组排序后,相等元素顺序可能改变
    • 大数据量排序时,注意 Collections.sort() 的内存开销
  • 💡 性能优化
    • 包装类型集合可转为基本类型数组排序
    • 超大数据集考虑并行排序(Arrays.parallelSort()

深入理解这两种排序方法的实现差异,能帮助我们在实际开发中做出更精准的技术选型,避免因算法特性导致的隐性 Bug,同时最大化利用 JVM 的底层优化能力。


原始标题:Difference Between Arrays.sort() and Collections.sort() | Baeldung