今天学习的时候,看到一篇文章,说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的整体思维导图如下:
总结
在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");
}
}