首页 > 编程语言 >Java性能优化-测试数组和链表在查询和添加删除时性能对比

Java性能优化-测试数组和链表在查询和添加删除时性能对比

时间:2023-07-16 14:33:26浏览次数:72  
标签:Java int 性能 Benchmark 链表 startIndex void public

场景

Java中使用JMH(Java Microbenchmark Harness 微基准测试框架)进行性能测试和优化:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/131723751

上面在使用JMH时测试了Java中数组和链表在进行头部插入时的对比结果。

下面分别对比在头部、中部、尾部分别进行查询和添加/删除时的性能对比,因添加和删除流程类似,

所以只测试添加即可。

Java中数组与链表

数组

数组(Array)是由相同类型的元素(element)的集合所组成的数据结构,分配⼀块连续的内存来存储。

利⽤元素的索引(index)可以计算出该元素对应的存储地址。

数组的“连续”特征决定了它的访问速度很快,因为它是连续存储的,所以这就决定了它的存储位置就是固定的,

因此它的访问速度就很快。

⽽缺点它对内存的要求⽐较⾼,必须要找到⼀块连续的内存才⾏。

数组的另⼀个缺点就是插⼊和删除的效率⽐较慢,假如我们在数组的⾮尾部插⼊或删除⼀个数据,那么就

要移动之后的所有数据,这就会带来⼀定的性能开销数组还有⼀个缺点,它的⼤⼩固定,不能动态拓展。

链表

链表(Linked list)是⼀种常⻅的基础数据结构,是⼀种线性表,但是并不会按线性的顺序存储数据,

⽽是在每⼀个节点⾥存到下⼀个节点的指针(Pointer)。由于不必须按顺序存储,链表在插⼊的时候可

以达到 O(1) 的复杂度,⽐另⼀种线性表顺序表快得多,但是查找⼀个节点或者访问特定编号的节点则

需要 O(n) 的时间,⽽顺序表相应的时间复杂度分别是 O(logn) 和 O(1)。

链表是⼀个⽆需连续内存存储的数据结构,链表的元素有两个属性,⼀个是元素的值,另⼀个是指针,

此指针标记了下⼀个元素的地址。

链表主要分为以下⼏类:

单向链表

单向链表中包含两个域,⼀个信息域和⼀个指针域。这个链接指向列表中的下⼀个节点,⽽最后⼀个节点则指向⼀个空值。

双向链表

双向链表也叫双链表,双向链表中不仅有指向后⼀个节点的指针,还有指向前⼀个节点的指针,这样可以

从任何⼀个节点访问前⼀个节点,当然也可以访问后⼀个节点,以⾄整个链表。

循环链表

循环链表中第⼀个节点之前就是最后⼀个节点,反之亦然。

为什么会有单、双链表之分?

这个就要从链表的删除说起了,如果单向链表要删除元素的话,不但要找到删除的节点,还要找到删除节

点的上⼀个节点(通常称之为前驱),因为需要变更上⼀个节点中 next 的指针,但⼜因为它是单向链表,

所以在删除的节点中并没有存储上⼀个节点的相关信息,那么我们就需要再查询⼀遍链表以找到上⼀个节点,

这样就带来了⼀定的性能问题,所以就有了双向链表。

链表的优点:

1. 链表对内存的利⽤率⽐较⾼,⽆需连续的内存空间,即使有内存碎⽚,也不影响链表的创建;

2. 链表的插⼊和删除的速度很快,⽆需像数组⼀样需要移动⼤量的元素;

3. 链表⼤⼩不固定,可以很⽅便的进⾏动态扩展。

链表缺点

链表的主要缺点是不能随机查找,必须从第⼀个开始遍历,查找效率⽐较低,链表查询的时间复杂度是O(n)。

在 Java 语⾔中,数组的代表为 ArrayList ,⽽链表的代表为 LinkedList ,因此我们就⽤这两个对象来进⾏测试。

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi

实现

1、测试头部添加

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
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.LinkedList;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2,time = 1,timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5,time = 5,timeUnit = TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Thread)
//头部添加性能测试
public class ArrayListWithLinkedListTestHeaderAdd {

    private static final int maxSize = 10000; //测试循环次数
    private static final int operationSize = 100; //操作次数

    private static ArrayList<Integer> arrayList;
    private static LinkedList<Integer> linkedList;

    public static void main(String[] args) throws RunnerException {
        //启动基准测试
        Options opt = new OptionsBuilder()
                .include(ArrayListWithLinkedListTestHeaderAdd.class.getSimpleName())  //要导入的测试类
                .build();
        //执行测试
        new Runner(opt).run();
    }

    //@Setup作用于方法上,用于测试前的初始化工作
    //启动执行事件
    @Setup
    public void init(){
        arrayList = new ArrayList<Integer>();
        linkedList = new LinkedList<Integer>();
        for (int i = 0; i < maxSize; i++) {
            arrayList.add(i);
            linkedList.add(i);
        }
    }

    //用于回收某些资源
    @TearDown
    public void finish(){

    }

    @Benchmark
    public void addArrayByFirst(Blackhole blackhole){
        for (int i = 0; i < operationSize; i++) {
            arrayList.add(i,i);
        }
        //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题
        blackhole.consume(arrayList);
    }

    @Benchmark
    public void addLinkedByFirst(Blackhole blackhole){
        for (int i = 0; i < operationSize; i++) {
            linkedList.add(i,i);
        }
        //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题
        blackhole.consume(linkedList);
    }
}

测试结果输出如下:

Benchmark                                              Mode  Cnt        Score         Error  Units
ArrayListWithLinkedListTestHeaderAdd.addArrayByFirst   avgt    5  6597432.461 ± 8384921.207  ns/op
ArrayListWithLinkedListTestHeaderAdd.addLinkedByFirst  avgt    5   111880.375 ±  258245.983  ns/op

可以看到在进行头部插入时LinkedList的平均完成时间比ArrayList平均完成时间快了约60倍

2、测试中部添加

    @Benchmark
    public void addArrayByMiddle(Blackhole blackhole){
        int startIndex = maxSize /2 ;//获取中间值
        for (int i = startIndex; i < (startIndex + operationSize); i++) {
            arrayList.add(i,i);
        }
        //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题
        blackhole.consume(arrayList);
    }

    @Benchmark
    public void addLinkedByMiddle(Blackhole blackhole){
        int startIndex = maxSize /2 ;//获取中间值
        for (int i = startIndex; i < (startIndex + operationSize); i++) {
            linkedList.add(i,i);
        }
        //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题
        blackhole.consume(linkedList);
    }

测试结果

Benchmark                                              Mode  Cnt        Score         Error  Units
ArrayListWithLinkedListTestMiddleAdd.addArrayByMiddle   avgt    5  6703786.087 ± 9225973.854  ns/op
ArrayListWithLinkedListTestMiddleAdd.addLinkedByMiddle  avgt    5  1064823.250 ±  697306.502  ns/op

可以看到在进行中间插入时LinkedList的平均完成时间比ArrayList平均完成时间快了约7倍

3、测试尾部添加

    @Benchmark
    public void addArrayByTail(Blackhole blackhole){
        int startIndex = maxSize - 1 - operationSize ;
        for (int i = startIndex; i < (maxSize - 1); i++) {
            arrayList.add(i,i);
        }
        //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题
        blackhole.consume(arrayList);
    }

    @Benchmark
    public void addLinkedByTail(Blackhole blackhole){
        int startIndex = maxSize - 1 - operationSize ;
        for (int i = startIndex; i < (maxSize - 1); i++) {
            linkedList.add(i,i);
        }
        //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题
        blackhole.consume(linkedList);
    }

测试结果

Benchmark                                            Mode  Cnt        Score          Error  Units
ArrayListWithLinkedListTestTailAdd.addArrayByTail   avgt    5  6981011.770 ± 10173306.725  ns/op
ArrayListWithLinkedListTestTailAdd.addLinkedByTail  avgt    5  2180224.087 ±   913796.230  ns/op

可以看到在进行尾部插入时LinkedList的平均完成时间比ArrayList平均完成时间快了约3倍

4、测试头部查询

    @Benchmark
    public void searchArrayByFirst(){
        for (int i = 0; i < operationSize; i++) {
            arrayList.get(i);
        }
    }

    @Benchmark
    public void searchLinkedByFirst(){
        for (int i = 0; i < operationSize; i++) {
            linkedList.get(i);
        }
    }

测试结果

Benchmark                                                    Mode  Cnt     Score      Error  Units
ArrayListWithLinkedListTestHeaderSearch.searchArrayByFirst   avgt    5     3.578 ±    0.835  ns/op
ArrayListWithLinkedListTestHeaderSearch.searchLinkedByFirst  avgt    5  4232.832 ± 2019.900  ns/op

5、测试中部查询

    @Benchmark
    public void searchArrayByMiddle(){
        int startIndex = maxSize / 2;
        int endIndex = startIndex + operationSize;
        for (int i = startIndex; i < endIndex; i++) {
            arrayList.get(i);
        }
    }

    @Benchmark
    public void searchLinkedByMiddle(){
        int startIndex = maxSize / 2;
        int endIndex = startIndex + operationSize;
        for (int i = startIndex; i < endIndex; i++) {
            linkedList.get(i);
        }
    }

测试结果

Benchmark                                                     Mode  Cnt        Score        Error  Units
ArrayListWithLinkedListTestMiddleSearch.searchArrayByMiddle   avgt    5        4.969 ±      6.568  ns/op
ArrayListWithLinkedListTestMiddleSearch.searchLinkedByMiddle  avgt    5  1131641.772 ± 313408.389  ns/op

可以看到在进行中部查询时ArrayList的平均完成时间比LinkedList平均完成时间快了约230947倍

6、测试尾部查询

    @Benchmark
    public void searchArrayByTail(){
        for (int i = maxSize - operationSize; i < maxSize; i++) {
            arrayList.get(i);
        }
    }

    @Benchmark
    public void searchLinkedByTail(){
        for (int i = maxSize - operationSize; i < maxSize; i++) {
            linkedList.get(i);
        }
    }

测试结果

Benchmark                                                 Mode  Cnt     Score     Error  Units
ArrayListWithLinkedListTestTailSearch.searchArrayByTail   avgt    5     5.074 ±   5.384  ns/op
ArrayListWithLinkedListTestTailSearch.searchLinkedByTail  avgt    5  5689.329 ± 563.806  ns/op

可以看到在进行尾部查询时ArrayList的平均完成时间比LinkedList平均完成时间快了约1100倍

7、总结:Java中是否是否链表比数组慢了1000多倍?

可以看到在进行头部查询时ArrayList的平均完成时间比LinkedList平均完成时间快了约1410倍

我们在最后的评测中可以看出,当数据初始化完成之后,我们再进⾏插⼊操作时,尤其是从头部插⼊时,

因为数组要移动之后的所有元素,因此性能要⽐链表低很多;但在查询时性能刚好相反,因为链表要遍历查询,

并且LinkedList 是双向链表,所以在中间查询时性能要⽐数组查询慢了上万倍(查询 100 个元素),

⽽两头查询(⾸部和尾部)时,链表也⽐数组慢了将近 1000 多倍(查询 100 个元素)。

因此在查询⽐较多的场景中,我们要尽量使⽤数组,⽽在添加和删除操作⽐较多时,我们应该使⽤链表结构。

标签:Java,int,性能,Benchmark,链表,startIndex,void,public
From: https://www.cnblogs.com/badaoliumangqizhi/p/17557837.html

相关文章

  • 性能分析工具总结
    CPU内存I/O参考资料Linux性能优化实战......
  • Java在指定位置添加字符串
    Java在指定位置添加字符串的实现作为一名经验丰富的开发者,我很乐意教会刚入行的小白如何在Java中实现在指定位置添加字符串的操作。在本篇文章中,我将按照以下步骤详细说明整个实现过程:获取原始字符串创建一个StringBuilder对象使用StringBuilder的insert()方法在指定位置插入......
  • Java语言支付代码
    Java语言支付代码引言随着电子商务的迅速发展,支付功能成为了每个电商平台必备的功能之一。在Java语言中,开发者可以使用各种支付SDK和API来实现支付功能。本文将介绍Java语言中支付代码的基本原理,并提供一些示例代码以帮助读者更好地理解。支付流程在介绍具体的支付代码前,我们先......
  • Java项目压测 链接被拒绝
    Java项目压测-链接被拒绝在进行Java项目压测时,有时会遇到“链接被拒绝”的问题。这意味着在压测过程中,无法与目标服务器建立连接。本文将介绍一些可能导致此问题的原因,并提供相应的代码示例来解决这个问题。原因一:服务器资源不足当服务器资源不足时,无法处理大量的并发请求,会导......
  • Java图片去噪
    Java图片去噪介绍图片去噪是一种常见的图像处理技术,可以帮助我们减少图片中的噪点,提高图像的质量和清晰度。在Java中,我们可以利用一些图像处理库来实现图片去噪的功能。本文将为你介绍如何使用Java实现图片去噪的步骤和相应的代码。流程下面是实现“Java图片去噪”的流程:步......
  • Java提供三方接口对接
    Java提供三方接口对接在现代软件开发中,很常见需要与第三方服务或接口进行对接。Java作为一种跨平台的编程语言,提供了许多开箱即用的工具和库,使得与第三方接口对接变得相对简单。接口对接的基本概念在软件开发中,接口对接是指将一个系统或应用程序与另一个系统或应用程序连接在一......
  • Java数组指针
    Java数组指针在Java中,数组是一种非常常见和重要的数据结构。数组允许我们在一个变量中存储多个相同类型的元素。但是,在使用数组时,有时候我们可能需要引用数组的指针,以便更方便地操作数组的元素。本文将介绍Java中的数组指针的概念,并提供相关的代码示例。什么是数组指针?在Java中,......
  • Java数据清洗
    Java数据清洗流程步骤一:导入所需的库和类首先,我们需要导入所需的库和类。在Java中,数据清洗通常会使用到以下库和类:importjava.io.BufferedReader;//用于读取文件importjava.io.BufferedWriter;//用于写入文件importjava.io.FileReader;//用于读取文本文件importjav......
  • 个人GAN训练的性能迭代
    使用GAN进行生成图片损失函数的迭代DCGAN->WassersteinGAN->WassersteinGAN+GradientPenaltyDiscriminator训练代码编写的细节:真图像和假图像要分批送入Discriminator,分批计算梯度(后面算出的梯度会累加到前面的梯度上面)。模型的迭代UpsampleMethodTransposedconvolu......
  • WSL环境中安装过Java并配置VSCode
    WSL环境已经配置好,现在开始在Ubuntu里面安装Java!一下载tar包方式手动安装1.1下载地址:https://www.oracle.com/java/technologies/downloads/1.2在上面的地址中选择合适的版本进行下载,然后将下载的安装包拷贝到wsl系统所在目录,然后进入Ubuntu,找到拷贝的安装包,比如我的wsl迁移......