跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
Javajava算法

深入解析:为什么 list.sort() 比 Stream().sorted() 更快

list.sort() 与 Stream().sorted() 性能差异源于 Stream 的额外转换开销及排序机制的不同。简单计时受 JVM 预热影响不可靠,需使用 JMH 基准测试。实测表明 list.sort() 效率更高,主要因为 Stream 涉及转流、收集及中间操作链的状态维护,而原生排序直接作用于数组且无包装层。实际开发中小数据量无需过度优化,大数据量建议优先使用原生方法。

FrontendX发布于 2025/2/230 浏览
深入解析:为什么 list.sort() 比 Stream().sorted() 更快

看到评论区常有人讨论 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 源码可以直观地看到差异:

  1. begin 方法初始化数组。
  2. accept 接收上游数据。
  3. end 方法开始进行排序。
  4. 遍历向下游发送数据。

Stream 的 sorted 操作内部调用了原生的排序逻辑,但它是在一个中间操作链中进行的。这意味着它需要维护 Spliterator 状态,涉及更多的对象创建和方法调用。而 list.sort() 直接作用于底层数组,使用的是优化的 Dual-Pivot Quicksort,没有中间层的包装开销。

虽然量化具体多出了多少 CPU 周期需要编译 JDK 源码并在关键节点打点,但原理上已经很清楚:stream() 排序所需时间肯定大于原生排序时间。

补充说明:

  1. 本文讨论的 stream() 指的是串行流,并行流(parallel stream)的情况另当别论。
  2. 绝大多数业务场景下,几百几千几万的数据量,性能差异微乎其微。开心就好,怎么方便怎么写,没必要为了这点性能差异过度纠结。

目录

  1. 真的更好吗?
  2. 流本身的损耗
  3. 排序过程
  • 💰 8折买阿里云服务器限时8折了解详情
  • 💰 8折买阿里云服务器限时8折购买
  • 🦞 5分钟部署阿里云小龙虾了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog

更多推荐文章

查看全部
  • 基于 Django 与 Vue 的大学生兼职管理系统设计与实现
  • 算法题解:LeetCode 389 找不同
  • 使用 Python 字典处理文本文件并上传至 Web 服务
  • 基于 Prefect 框架的 Python 可视化爬虫项目实战
  • 计算机图形学:基础概念与技术概览
  • 不改一行代码定位线上 Java 性能问题
  • 大模型内在推理能力探索:无需提示的思考链解码
  • Java 集合框架详解:核心接口与常用实现类
  • Vue 滑块验证组件实战:支持自定义图片与拖拽交互
  • JavaScript 开发常用工具函数精选
  • Spring 启动报错:Could not resolve placeholder jdbc.url 解决方案
  • 在 Kubernetes 中部署 Kafka 高可用集群实战
  • ROS 核心组件与工具概览
  • Scala 核心知识点梳理:从基础语法到集合框架
  • Pandas 入门核心技巧与实战指南
  • Docker 重新打包 MySQL5.7 镜像并定制配置
  • Vue3 跨层级组件通信:Provide 与 Inject 机制详解
  • Direct3D 融合技术与透明效果实现
  • Linux 磁盘 I/O 性能测试:iozone 安装与实战详解
  • Android Handler 消息传递机制深度解析

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online