看到评论区常有人讨论 list.sort() 和 list.stream().sorted() 的性能差异。普遍共识是前者更快,但具体原因往往语焉不详。今天咱们不盲从结论,先问是不是,再问为什么。
真的更好吗?
先写个简单的 Demo 验证一下。
List<Integer> userList = new ArrayList<>();
Random rand = new Random();
for (int i = 0; i < 10000; i++) {
userList.add(rand.nextInt(1000));
}
List<Integer> userList2 = new ArrayList<>();
userList2.addAll(userList);
Long startTime1 = System.currentTimeMillis();
userList2.stream().sorted(Comparator.comparing(Integer::intValue)).collect(Collectors.toList());
System.out.println("stream.sort 耗时:" + (System.currentTimeMillis() - startTime1) + "ms");
Long startTime = System.currentTimeMillis();
userList.sort(Comparator.comparing(Integer::intValue));
System.out.println("List.sort() 耗时:" + (System.currentTimeMillis() - startTime) + "ms");
输出结果可能是:
stream.sort 耗时:62ms
List.sort() 耗时:7ms
看起来原生排序确实快很多。但如果交换顺序执行呢?
// ... 初始化代码同上 ...
Long startTime = System.currentTimeMillis();
userList.sort(Comparator.comparing(Integer::intValue));
System.out.println("List.sort() 耗时:" + (System.currentTimeMillis() - startTime) + "ms");
Long startTime1 = System.currentTimeMillis();
userList2.stream().sorted(Comparator.comparing(Integer::intValue)).collect(Collectors.toList());
System.out.println("stream.sort 耗时:" + (System.currentTimeMillis() - startTime1) + "ms");
这次结果可能变成:
List.sort() 耗时:68ms
stream.sort 耗时:13ms
这就尴尬了,先后顺序不同,结果波动巨大。这说明简单的 System.currentTimeMillis() 计时受 JVM 预热、JIT 编译影响太大,不足以作为严谨依据。
基准测试(Benchmark)的核心在于让被测代码充分预热,消除 JIT 优化带来的干扰。我们改用 JMH 框架来测试集合大小在 100、10000、100000 时的性能差异。
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 2, time = 1)
@Measurement(iterations = 5, time = 5)
@Fork(1)
@State(Scope.Thread)
public class SortBenchmark {
@Param(value = {"100", "10000", "100000"})
private int operationSize;
private static List<Integer> arrayList;
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(SortBenchmark.class.getSimpleName())
.result("SortBenchmark.json")
.mode(Mode.All)
.resultFormat(ResultFormatType.JSON)
.build();
new Runner(opt).run();
}
@Setup
public void init() {
arrayList = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < operationSize; i++) {
arrayList.add(random.nextInt(10000));
}
}
@Benchmark
public void sort(Blackhole blackhole) {
arrayList.sort(Comparator.comparing(e -> e));
blackhole.consume(arrayList);
}
@Benchmark
public void streamSorted(Blackhole blackhole) {
arrayList = arrayList.stream().sorted(Comparator.comparing(e -> e)).collect(Collectors.toList());
blackhole.consume(arrayList);
}
}
经过 JMH 跑出来的数据表明,list.sort() 的效率确实稳定优于 stream().sorted()。那核心差距到底在哪?
流本身的损耗
Stream API 确实让代码优雅了不少,类似 SQL 的聚合操作在应用层就能实现。但代价是额外的开销:把 List 转成 Stream,处理完还得收集回 List。这部分转换成本有多大?
我们单独剥离出'转流 + 收集'的过程做对比测试。
// ... 引入必要的包 ...
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 2, time = 1)
@Measurement(iterations = 5, time = 5)
@Fork(1)
@State(Scope.Thread)
public class SortBenchmark3 {
@Param(value = {"100", "10000"})
private int operationSize;
private static List<Integer> arrayList;
// ... main 方法省略 ...
@Setup
public void init() {
arrayList = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < operationSize; i++) {
arrayList.add(random.nextInt(10000));
}
}
@Benchmark
public void stream(Blackhole blackhole) {
arrayList.stream().collect(Collectors.toList());
blackhole.consume(arrayList);
}
@Benchmark
public void sort(Blackhole blackhole) {
arrayList.stream().sorted(Comparator.comparing(Integer::intValue)).collect(Collectors.toList());
blackhole.consume(arrayList);
}
}
测试结果显示,集合转流再收集确实耗时,但这部分开销占全过程的比例并不算高。所以,这只是造成性能差异的一小部分原因。
排序过程
真正的瓶颈在于排序机制本身。通过查看 JDK 源码可以直观地看到差异:
begin方法初始化数组。accept接收上游数据。end方法开始进行排序。- 遍历向下游发送数据。
Stream 的 sorted 操作内部调用了原生的排序逻辑,但它是在一个中间操作链中进行的。这意味着它需要维护 Spliterator 状态,涉及更多的对象创建和方法调用。而 list.sort() 直接作用于底层数组,使用的是优化的 Dual-Pivot Quicksort,没有中间层的包装开销。
虽然量化具体多出了多少 CPU 周期需要编译 JDK 源码并在关键节点打点,但原理上已经很清楚:stream() 排序所需时间肯定大于原生排序时间。
补充说明:
- 本文讨论的
stream()指的是串行流,并行流(parallel stream)的情况另当别论。 - 绝大多数业务场景下,几百几千几万的数据量,性能差异微乎其微。开心就好,怎么方便怎么写,没必要为了这点性能差异过度纠结。


