作者:是奉壹呀 \
起源:juejin.cn/post/7262274383287500860
看到一个评论,外面提到了list.sort()和list.strem().sorted()排序的差别。
说到list sort()排序比stream().sorted()排序性能更好,但没说到为什么。
有敌人也提到了这一点。本文从新开始,先问是不是,再问为什么。
举荐一个开源收费的 Spring Boot 实战我的项目:
https://github.com/javastacks/spring-boot-best-practice
真的更好吗?
先简略写个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耗时:62msList.sort()耗时:7ms
由此可见list原生排序性能更好。
能证实吗?
证据错了。
再把demo变换一下,先输入stream.sort
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 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()耗时:68msstream.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<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); }}
性能测试后果:
能够看到,list sort()效率的确比stream().sorted()要好。
为什么更好?
流自身的损耗
java的stream让咱们能够在应用层就能够高效地实现相似数据库SQL的聚合操作了,它能够让代码更加简洁优雅。
然而,假如咱们要对一个list排序,得先把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<Integer> 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 < 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); }}
办法stream测试将一个汇合转为流再收集回来的耗时。
办法sort测试将一个汇合转为流再排序再收集回来的全过程耗时。
测试后果如下:
能够发现,汇合转为流再收集回来的过程,必定会耗时,然而它占全过程的比率并不算高。
因而,这部只能说是小部份的起因。
排序过程
咱们能够通过以下源码很直观的看到。
- 1 begin办法初始化一个数组。
- 2 accept 接管上游数据。
- 3 end 办法开始进行排序。
这里第3步间接调用了原生的排序办法,实现排序后,第4步,遍历向上游发送数据。
所以通过源码,咱们也能很显著地看到,stream()排序所需工夫必定是 > 原生排序工夫。
只不过,这里要量化地搞明确,到底多出了多少,这里得去编译jdk源码,在第3步前后将工夫打印进去。
这一步我就不做了。
感兴趣的敌人能够去测一下。
不过我感觉这两点也能很好地答复,为什么list.sort()比Stream().sorted()更快。
补充阐明:
1、 本文说的stream()流指的是串行流,而不是并行流;
2、 绝大多数场景下,几百几千几万的数据,开心就好,怎么不便怎么用,没有必要去计较这点性能差别;
近期热文举荐:
1.1,000+ 道 Java面试题及答案整顿(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!
5.《Java开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!