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

今天学习的时候,看到一篇文章,说LinkedList的作者自己不用LinkedList,感觉十分的吃惊。
Java基础(一)-ArrayList和LinkedList性能对比与原因探索_第1张图片
本着实践出真知的态度,立刻写了几行代码用于测试ArrayList和LinkedList的在增删查改四大方面的性能对比(测试所有代码放在文末)。经过一番测试后,我得到了如下的对比图(由于随即插入,查找,删除,修改等操作LinkedList的时间简直慢得令人发指,因此只测试了在大量数据情况下对少量数据的更改操作):
Java基础(一)-ArrayList和LinkedList性能对比与原因探索_第2张图片

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

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

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

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

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

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

LinkedList源码探索

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

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

Java基础(一)-ArrayList和LinkedList性能对比与原因探索_第3张图片

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中主要初始化及增删查改实现逻辑如下:

Java基础(一)-ArrayList和LinkedList性能对比与原因探索_第4张图片

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

ArrayList的整体思维导图如下:

Java基础(一)-ArrayList和LinkedList性能对比与原因探索_第5张图片

Java基础(一)-ArrayList和LinkedList性能对比与原因探索_第6张图片

总结

在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
 * 
date 2022-03-17 */ public class listCom { //LinkedList和ArrayList性能对比 public static void main(String[] args){ //System.out.println( "" + (4 >> 1) ); //初始化两列表 ArrayList arrayList = new ArrayList<>(); LinkedList linkedList = new LinkedList<>(); //尾部插入增操作性能对比 //1.ArrayList增加操作 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增加操作 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增加操作 List 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增加操作 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查询操作 preTime = System.currentTimeMillis(); for(int i:listRandom){ arrayList.get(i); } afterTime = System.currentTimeMillis(); System.out.println("ArrayList 查找100个元素所用时(ms):" + (afterTime - preTime)); //2.LinkedList查询操作 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删除操作 preTime = System.currentTimeMillis(); for(int i : listRandom){ arrayList.remove(i); } afterTime = System.currentTimeMillis(); System.out.println("ArrayList 删除100个元素所用时(ms):" + (afterTime - preTime)); //2.LinkedList删除操作 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修改操作 preTime = System.currentTimeMillis(); for(int i : listRandom){ arrayList.set(i,i); } afterTime = System.currentTimeMillis(); System.out.println("ArrayList 修改100个元素所用时(ms):" + (afterTime - preTime)); //2.LinkedList修改操作 preTime = System.currentTimeMillis(); for(int i : listRandom){ linkedList.set(i,i); } afterTime = System.currentTimeMillis(); System.out.println("LinkedList 修改100个元素所用时(ms):" + (afterTime - preTime) + "\n"); } }

你可能感兴趣的:(Java,java,链表,开发语言)