Java基础(一)-ArrayList和LinkedList性能对比与原因探索

今天学习的时候,看到一篇文章,说LinkedList的作者自己不用LinkedList,感觉十分的吃惊。

本着实践出真知的态度,立刻写了几行代码用于测试ArrayList和LinkedList的在增删查改四大方面的性能对比(测试所有代码放在文末)。经过一番测试后,我得到了如下的对比图(由于随即插入,查找,删除,修改等操作LinkedList的时间简直慢得令人发指,因此只测试了在大量数据情况下对少量数据的更改操作):
在这里插入图片描述

有意思的是,在我们学习数组和链表上,经常有如下结论:

  • 数组随机访问快,但随即插入删除较慢;

  • 链表插入快,但随机访问速率较慢;

  • 数组适合查询频繁的需求,而链表适合用于增删频繁的场景;

但是观察实际测试的结果,ArrayList除了在尾插上和LinkedList几乎不相上下的速度,在其他方面速度几乎可以说是吊打LinkedList。

遂决定翻阅LinkedList和ArrayList源码,查看他们的具体实现逻辑,找出他们性能差异较大的原因。

LinkedList源码探索

LinkedList底层是用双向链表实现的,他包含一个记录链表长度的属性size,一个记录首节点的指针first和一个记录尾节点的指针last。并包含一个继承自AbstractSequentialList的属性modCount,用于记录对LinkedList的size造成改变的操作次数。

LinkedList中增删查改方法的实现逻辑如下:

在这里插入图片描述

ArrayList源码探索

ArrayList的底层使用Object[]实现,下面详细讲讲ArrayList中比较重要的一些问题

ArrayList中的属性

在ArrayList中包含几个特殊属性,他们在ArrayList的初始化和ArrayList的最大容量限定上起着很大作用,下面具体解析一下ArrayList中的几大属性:

  • DEFAULT_CAPACITY :数组默认初始化容量,在ArrayList中初始化大小为10;
  • EMPTY_ELEMENTDATA:用于空实例的共享空数组实例。当初始化一个ArrayList时(尚未往ArrayList中添加元素),使用EMPTY_ELEMENTDATA为初始化数组;
  • DEFAULTCAPACITY_EMPTY_ELEMENTDATA:用于默认大小的空实例的共享空数组实例, 将此与 EMPTY_ELEMENTDATA 区分开来,以了解添加第一个元素时要膨胀多少。当初始化一个默认容量的ArrayList时,使用该数组(数组原长度为0,当往里面添加第一个元素时,size膨胀为默认容量10);
  • elementData:实际存储数组;
  • size:数组中含有元素个数;
  • MAX_ARRAY_SIZE ( = Integer.MAX_VALUE - 8):定义了ArrayList的最大容量。由于索引是int类型,因此数组最大值最大为2^31 - 1(即Integer.MAX_VALUE)。但由于部分VMs除了要存储自身数据外还需要最多32bytes来存储对象头信息,因此需要减8(8位int即32bytes)

ArrayList中的方法实现

ArrayList中主要初始化及增删查改实现逻辑如下:

在这里插入图片描述

**ArrayList的扩容:**在往ArrayList中插入数据时,有可能导致ArrayList发生扩容。当当前数组容量不足以存储所有数据时,会触发ArrayList的grow()方法进行扩容。默认扩容为原数组的1.5倍。若扩容后仍不足以存储所有数据,则扩容置所需的最小容量。若扩容后大于ArrayList最大容量MAX_ARRAY_SIZE。则扩容至Integer.MAX_VALUE(注:这里没有预留32位存储对象头信息,因此对部分VMs可能导致溢出)。

ArrayList的整体思维导图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M93YuQkV-1647583376312)(C:\Users\123\AppData\Roaming\Typora\typora-user-images\image-20220318135659862.png)]

在这里插入图片描述

总结

在LinkedList中进行随机插入和删除,查询,修改操作都需要先折半遍历链表(时间复杂度为O(n)),而在ArrayList中查询和修改只需要O(1)时间。在删除和随机插入需要对数组进行线性时间操作,因此其性能较LinkedList更高。此外,由于对ArrayList进行插入时,可能会导致扩容问题,在扩容时需要对数组进行全量复制到新数组中,较为影响性能。因此在实际中我们可以先估算ArrayList的大概容量,提前分配,减少其扩容花费时间。
(需要思维导图文件请私信)

下面是测试所用代码

package com.thread.demo.listCompare;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

/**
 * //TODO 添加类/接口功能描述
 *
 * @author Sc_Cloud
 * <br/>date 2022-03-17
 */
public class listCom {
    //LinkedList和ArrayList性能对比
    public static void main(String[] args){

        //System.out.println( "" + (4 >> 1) );

        //初始化两列表
        ArrayList<Integer> arrayList = new ArrayList<>();
        LinkedList<Integer> linkedList = new LinkedList<>();

        //尾部插入增操作性能对比
        //1.ArrayList<Integer>增加操作
        long preTime = System.currentTimeMillis();
        for(int i = 0;i < 10000000 ; ++i){
            arrayList.add(i);
        }
        long afterTime = System.currentTimeMillis();
        System.out.println("ArrayList 增加10000000个元素总用时(ms):" + (afterTime - preTime));

        //1.ArrayList<Integer>增加操作
        preTime = System.currentTimeMillis();
        for(int i = 0;i < 10000000 ; ++i){
            linkedList.add(i);
        }
        afterTime = System.currentTimeMillis();
        System.out.println("LinkedList 增加10000000个元素总用时(ms):" + (afterTime - preTime) + "\n");

        //随机插入增操作性能对比
        //1.ArrayList<Integer>增加操作
        List<Integer> listRandom = new ArrayList<>();
        Random random = new Random();
        for(int i = 0 ; i < 10000 ; ++i){
            listRandom.add(random.nextInt(9999999));
        }

        preTime = System.currentTimeMillis();
        for(int i = 0;i < 100 ; ++i){
            arrayList.add(listRandom.get(i),i);
        }
        afterTime = System.currentTimeMillis();
        System.out.println("ArrayList 随机插入100个元素总用时(ms):" + (afterTime - preTime));

        //1.ArrayList<Integer>增加操作
        preTime = System.currentTimeMillis();
        for(int i = 0;i < 100 ; ++i){
            linkedList.add(listRandom.get(i),i);
        }
        afterTime = System.currentTimeMillis();
        System.out.println("LinkedList 随机插入100个元素总用时(ms):" + (afterTime - preTime) + "\n");

        //查性能对比
        //为避免长生随机数可能造成的差异,这里先生成一个随机数列表,并随机查询
        listRandom = new ArrayList<>();
        random = new Random();
        for(int i = 0 ; i < 100 ; ++i){
            listRandom.add(random.nextInt(9999999));
        }

        //1.ArrayList<Integer>查询操作
        preTime = System.currentTimeMillis();
        for(int i:listRandom){
            arrayList.get(i);
        }
        afterTime = System.currentTimeMillis();
        System.out.println("ArrayList 查找100个元素所用时(ms):" + (afterTime - preTime));

        //2.LinkedList<Integer>查询操作
        preTime = System.currentTimeMillis();
        for(int i:listRandom){
            linkedList.get(i);
        }
        afterTime = System.currentTimeMillis();
        System.out.println("LinkedList 查找100个元素所用时(ms):" + (afterTime - preTime) + "\n");

        //删性能对比
        //重新生成一串随机数列,排除缓存影响
        listRandom.clear();
        for(int i = 0 ; i < 100 ; ++i){
            listRandom.add(random.nextInt(9999900));
        }

        //1.ArrayList<Integer>删除操作
        preTime = System.currentTimeMillis();
        for(int i : listRandom){
            arrayList.remove(i);
        }
        afterTime = System.currentTimeMillis();
        System.out.println("ArrayList 删除100个元素所用时(ms):" + (afterTime - preTime));

        //2.LinkedList<Integer>删除操作
        preTime = System.currentTimeMillis();
        for(int i : listRandom){
            linkedList.remove(i);
        }
        afterTime = System.currentTimeMillis();
        System.out.println("LinkedList 删除100个元素所用时(ms):" + (afterTime - preTime) + "\n");

        //改性能对比
        //重新生成一串随机数列,排除缓存影响
        listRandom.clear();
        for(int i = 0 ; i < 100 ; ++i){
            listRandom.add(random.nextInt(9999900));
        }

        //1.ArrayList<Integer>修改操作
        preTime = System.currentTimeMillis();
        for(int i : listRandom){
            arrayList.set(i,i);
        }
        afterTime = System.currentTimeMillis();
        System.out.println("ArrayList 修改100个元素所用时(ms):" + (afterTime - preTime));

        //2.LinkedList<Integer>修改操作
        preTime = System.currentTimeMillis();
        for(int i : listRandom){
            linkedList.set(i,i);
        }
        afterTime = System.currentTimeMillis();
        System.out.println("LinkedList 修改100个元素所用时(ms):" + (afterTime - preTime) + "\n");
    }

}
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值