首页 > 其他分享 >为什么list.sort()比Stream().sorted()更快?

为什么list.sort()比Stream().sorted()更快?

时间:2023-08-01 20:12:22浏览次数:311  
标签:sort Stream stream arrayList list System new import

昨天写了一篇文章《小细节,大问题。分享一次代码优化的过程》,里面提到了list.sort()和list.strem().sorted()排序的差异。
说到list sort()排序比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

由此可见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()耗时: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<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. 绝大多数场景下,几百几千几万的数据,开心就好,怎么方便怎么用,没有必要去计较这点性能差异。

标签:sort,Stream,stream,arrayList,list,System,new,import
From: https://www.cnblogs.com/eryuan/p/17598880.html

相关文章

  • QListWidget样式
    /*设置垂直滚动的全局属性值*/QScrollBar:vertical{  width:10px;  background:rgba(0,0,0,0%);  margin:0px,0px,0px,0px;  padding-top:0px;  padding-bottom:0px;}/*设置垂直滚动的滚动条属性值*/QScrollBar::handle:vertical{  width:10px;  back......
  • freemeker 遍历map嵌套list数据结构
    遍历嵌套数据结构渲染map中value是list的内容<#ifnodes??&&(nodes?size>0)>【节点明细】<#listnodes?keysasalarmLevel>${alarmLevel+":"}<#if(nodes[alarmLevel])??><#list(nodes[alarmLevel])asnode>${node.nodeNo}<#sep>,&......
  • 拼多多店铺订单API接口(pdd.order.basic.list.get订单基础信息列表查询接口)代码对接教
    拼多多店铺订单API接口(pdd.order.basic.list.get订单基础信息列表查询接口)代码对接教程如下:1.公共请求参数参数名称参数类型是否必填参数描述(接口代码教程wx19970108018)typeString必填API接口名称(点击获取请求key和secret)client_idString必填POP分配给应用的client_idaccess_tok......
  • Best Heavy Duty Truck Diagnostic Software Of 2023 Completed List
    Diagnostictoolsareessentialintheautomotiveindustryforidentifyingandresolvingissueswithvehicles.Thesetoolsprovidetechnicianswiththenecessaryinformationtodiagnoseandrepairproblemsefficiently.Inthisarticle,wewillexplorethe......
  • 2023-8-1 WPF的ItemsControl容器(DataGrid,ListBox,ListView等)可以实现的隔行样式修改
    实现的隔行样式修改【作者】长生微软官方文档详细介绍实现方式如果需要让你的wpf表格或者间隔样式实现下列效果可以使用AlternationCount首先添加一个DataGrid,并使用AlternatingRowBackground设置奇数行的背景色为紫色<DataGridAlternatingRowBackground="Purple"></Data......
  • java两个list对象取差集
    在Java开发中,我们经常会遇到需要对两个List对象进行比较并取得它们的差集的情况。差集是指在第一个List中出现而在第二个List中没有出现的元素集合。本文将介绍如何使用Java来实现这个功能,并提供代码示例。方法一:使用循环嵌套遍历比较一种常见的方法是通过嵌套循环遍历比较两个Li......
  • 记录每一天,流式编程 stream
    流式编程stream我以为遇到lambda已经够让我惊喜了,万万没想到stream的出现,才让我发现它才是函数式编程的主角。特点:无存储。stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/Ochannel等。对stream的任何修改都不会修......
  • ArrayList源码
    add方法publicArrayList(){this.elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}//添加元素publicbooleanadd(Ee){ensureCapacityInternal(size+1);//确保数组容量足够添加elementData[size++]=e;returntrue;}调用add方法往Array......
  • show processlist 命令
    SHOWPROCESSLIST是一个MySQL命令,用于显示当前连接到MySQL服务器的所有活动进程的信息。以下是SHOWPROCESSLIST命令的详细用法:SHOWPROCESSLIST;这个命令将返回一个结果集,其中包含每个活动进程的一行记录。每行记录包含了以下列:Id:连接标识符(connectionidentifier)。User......
  • nginx日志中request_time小于upstream_response_time
    如题,最近监测nginx日志时,发现request_time小于upstream_response_time,如图, 正常情况下,request_time是从接受用户请求的第一个字节到发送完响应数据的时间,upstream_response_time是nginx向后端建立连接开始到接受完数据然后关闭连接为止的时间,按常理推断request_time要大于upstr......