行业资讯 2025年08月6日
0 收藏 0 点赞 854 浏览 5828 个字
摘要 :

文章目录 list sort()排序真的比stream().sorted()更好吗? 性能基准测试结果 为什么更好? 流本身的损耗 排序过程 总结 List集合原生的排序性能和stream串行流的……




  • list sort()排序真的比stream().sorted()更好吗?
    • 性能基准测试结果
  • 为什么更好?
    • 流本身的损耗
    • 排序过程
  • 总结

List集合原生的排序性能和stream串行流的排序性能谁更胜一筹呢?有朋友说到list.sort()排序比stream().sorted()排序性能更好。接下里我们一起用代码来重新认识这一观点!

list sort()排序真的比stream().sorted()更好吗?

先简单写个demo

ListuserList = new ArrayList();
        Random rand = new Random();
        for (int i = 0; i 
            userList.add(rand.nextInt(1000));
        }
        ListuserList2 = 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

由此可见list原生排序性能更好。
能证明吗?
证据错了。

再把demo变换一下,先输出stream.sort

ListuserList = new ArrayList();
        Random rand = new Random();
        for (int i = 0; i 
            userList.add(rand.nextInt(1000));
        }
        ListuserList2 = new ArrayList();
        userList2.addAll(userList);

        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

这能证明上面的结论错误了吗?
都不能。
两种方式都不能证明什么。

使用这种方式在很多场景下是不够的,某些场景下,JVM会对代码进行JIT编译和内联优化。

Long startTime = System.currentTimeMillis();
...
System.currentTimeMillis() - startTime

此时,代码优化前后执行的结果就会非常大。

基准测试是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试。

基准测试使得被测试代码获得足够预热,让被测试代码得到充分的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 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 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);
    }

}

性能基准测试结果

为什么list.sort()性能比Stream().sorted()更好?
可以看到,list sort()效率确实比stream().sorted()要好。

为什么更好?

流本身的损耗

Java的Stream使我们能够在应用层轻松高效地执行类似于数据库SQL的聚合操作。这种优雅的编程范式使代码变得更加简洁。

然而,在某些情况下需要注意效率。举个例子,如果我们想对一个List进行排序,就需要先将其转换为一个Stream流,完成排序后还需要将数据收集起来并重新构建成List。这个过程中会带来额外的开销,那么这个开销究竟有多大呢?

我们可以通过以下代码来进行基准测试

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.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
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 SortBenchmark3 {

    @Param(value = {\"100\", \"10000\"})
    private int operationSize; // 操作次数


    private static List arrayList;

    public static void main(String[] args) throws RunnerException {
        // 启动基准测试
        Options opt = new OptionsBuilder()
                .include(SortBenchmark3.class.getSimpleName()) // 要导入的测试类
                .result(\"SortBenchmark3.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 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);
    }
 
}

方法stream测试将一个集合转为流再收集回来的耗时。

方法sort测试将一个集合转为流再排序再收集回来的全过程耗时。

测试结果如下:
为什么list.sort()性能比Stream().sorted()更好?
可以发现,集合转为流再收集回来的过程,肯定会耗时,但是它占全过程的比率并不算高。

因此,这部只能说是小部份的原因。

排序过程

为什么list.sort()性能比Stream().sorted()更好?
通过检查代码,我们可以直观地理解排序过程如下:

  1. 初始化过程begin方法)设置了一个数组的起始状态。
  2. 接受数据accept方法)用于接收来自上游的数据。
  3. 排序过程end方法)开始进行排序操作。在这一步中,直接调用了底层的排序方法,完成数据排序。排序完成后,我们进入第4步。
  4. 数据发送,数据按照排序结果逐个发送给下游。

因此,通过对源码的分析,我们能够清楚地看到,在排序操作方面,stream() 的排序操作所需的时间肯定大于直接调用原生排序方法。

尽管如此,我们要考虑量化这种差异,需要对JDK源代码进行编译,然后在第3步之前和之后插入时间记录,以精确衡量时间开销。

这一步虽然略显繁琐,但对于那些对细节感兴趣的人来说,是一个值得尝试的实践。

最后,我认为这两个观点可以很好地解释为什么 list.sort()Stream().sorted() 更加高效。前者能够避免Stream操作的一些开销,从而在性能上表现更好。不过,我们也要记住,Stream操作在很多情况下更注重代码的简洁性和可读性,而不是追求绝对的性能。所以,在实际应用中,根据情况权衡选择是很重要的。

总结

实际上,本文说的stream()流指的是串行流,而不是并行流。Stream在内部会通过迭代器来遍历数据并执行相应的操作,这可能会引入一些额外的性能开销。尽管这些开销在大多数情况下是可以忽略不计的,但在某些特定情况下可能会对性能产生一些微弱的影响。在进行大规模数据处理时,特别是对于一些简单的操作,将Stream引入可能会稍微降低性能。此外,从Stream到集合类型的转换,例如将排序后的数据重新收集到List中,也会涉及到一些内部操作,从而引入一定程度的开销。

然而,需要强调的是,现代的JVM在对Stream操作进行优化方面已经取得了很大的进步。它们会尽量减少额外的开销,并针对各种操作模式进行了优化。绝大多数场景下,几百几千几万的数据,开心就好,怎么方便怎么用,没有必要去计较这点性能差异。因此,一般情况下,使用Stream进行操作不会显著影响性能。如果性能对于特定应用场景非常关键,可以考虑使用传统的循环方式来处理数据,以获得更精细的控制。

微信扫一扫

支付宝扫一扫

版权: 转载请注明出处:https://www.zuozi.net/9051.html

管理员

相关推荐
2025-08-06

文章目录 一、Reader 接口概述 1.1 什么是 Reader 接口? 1.2 Reader 与 InputStream 的区别 1.3 …

988
2025-08-06

文章目录 一、事件溯源 (一)核心概念 (二)Kafka与Golang的优势 (三)完整代码实现 二、命令…

465
2025-08-06

文章目录 一、证明GC期间执行native函数的线程仍在运行 二、native线程操作Java对象的影响及处理方…

348
2025-08-06

文章目录 一、事务基础概念 二、MyBatis事务管理机制 (一)JDBC原生事务管理(JdbcTransaction)…

456
2025-08-06

文章目录 一、SnowFlake算法核心原理 二、SnowFlake算法工作流程详解 三、SnowFlake算法的Java代码…

517
2025-08-06

文章目录 一、本地Jar包的加载操作 二、本地Class的加载方法 三、远程Jar包的加载方式 你知道Groo…

832
发表评论
暂无评论

还没有评论呢,快来抢沙发~

助力内容变现

将您的收入提升到一个新的水平

点击联系客服

在线时间:08:00-23:00

客服QQ

122325244

客服电话

400-888-8888

客服邮箱

122325244@qq.com

扫描二维码

关注微信客服号