ArrayList、LinkedList哪家强,据说90%人都不知道
写代码的时候很经常就会用到List集合,但是很多时候我看到童鞋们都是用ArrayList来作为实现类,很少用LinkedList,鉴于这两个集合使用频率特别高,所以老师给童靴们分析一下,他们在不同场景下面效率,谁低谁高。
学过数据结构的同学都知道,ArrayList采用的是线性表存储,LinkedList采用的链表存储,关于两则之前的区别,童鞋们可以自行了解。
一、ArrayList与LinkedList
我们主要研究以下几点:
- ArrayList多次扩容数组,对性能影响情况?
- 添加和删除哪一个更加高效?
- 各自内存使用情况,谁高谁低?
1.1、我们先来测试一下第一点,代码如下所示:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
int size = 1000 * 10000;
Long time1 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
list.add(i);
}
Long time2 = System.currentTimeMillis();
System.out.println("ArrayList----time:" + (time2 - time1));
}
结果:ArrayList----time:2493
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(1100 * 10000);
int size = 1000 * 10000;
Long time1 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
list.add(i);
}
Long time2 = System.currentTimeMillis();
System.out.println("ArrayList----time:" + (time2 - time1));
}
结果:ArrayList----time:560
public static void main(String[] args) {
List<Integer> list = new LinkedList<>();
int size = 1000 * 10000;
Long time1 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
list.add(i);
}
Long time2 = System.currentTimeMillis();
System.out.println("LinkedList----time:" + (time2 - time1));
}
结果:LinkedList----time:6302
结论:在数据量足够大的情况下,设置ArrayList的初始值要比没设置的添加速度快。但是初始值n并不是越大越好,如果设置过大,有可能会造成内存溢出,反而添加速度更慢。 但是如果ArrayList数据量特别小的情况下,二则相差无几,当然对计算机而言,如果没触发扩容二者都一样,如果触发了,就要比对谁触发的次数多,移动的数据量大。 相对ArrayList而言,LinkedList的添加速度慢,慢了差不多三倍。
1.2、添加和删除哪一个更加高效?
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
int size = 10 * 10000;
Long time1 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
list.add(i);
}
Long time2 = System.currentTimeMillis();
System.out.println("add----time:" + (time2 - time1));
for (int i = 0; i < list.size(); i++) {
list.remove(i);
i--;
}
Long time3 = System.currentTimeMillis();
System.out.println("remove----time:" + (time3 - time2));
}
结果:
add----time:5
remove----time:566
// LinkedList正序坐标删除
public static void main(String[] args) {
List<Integer> list = new LinkedList<>();
int size = 10 * 10000;
Long time1 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
list.add(i);
}
Long time2 = System.currentTimeMillis();
System.out.println("add----time:" + (time2 - time1));
for (int i = 0; i < list.size(); i++) {
list.remove(i);
i--;
}
Long time3 = System.currentTimeMillis();
System.out.println("remove----time:" + (time3 - time2));
}
结果:
add----time:4
remove----time:3
// LinkedList逆序坐标删除
public static void main(String[] args) {
List<Integer> list = new LinkedList<>();
int size = 10 * 10000;
Long time1 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
list.add(i);
}
Long time2 = System.currentTimeMillis();
System.out.println("add----time:" + (time2 - time1));
for (int i = 0; i < list.size(); i++) {
list.remove(list.size() - i - 1);
i--;
}
Long time3 = System.currentTimeMillis();
System.out.println("remove----time:" + (time3 - time2));
}
结果:
add----time:6
remove----time:3
// LinkedList逆序对象删除
public static void main(String[] args) {
List<Integer> list = new LinkedList<>();
int size = 10 * 10000;
Long time1 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
list.add(i);
}
Long time2 = System.currentTimeMillis();
System.out.println("add----time:" + (time2 - time1));
for (int i = 0; i < list.size(); i++) {
Integer integer = list.get(list.size() - i - 1);
list.remove(integer);
i--;
}
Long time3 = System.currentTimeMillis();
System.out.println("remove----time:" + (time3 - time2));
}
结果:
add----time:6
remove----time:12107
ArrayList因为添加触发到扩容,最终执行的是System.arraycopy本地方法,执行速度相对较快,但删除需要进行数据移动,所以执行效率低。
LinkedList正序坐标删除和逆序坐标删除,时间效率都一样,逆序坐标删除是因为程序做了特殊处理,但是如果是逆序对象删除的话,运行效率就会特别差,原理后面再介绍。
1.3、内存使用情况如何?
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
int size = 100 * 10000;
for (int i = 0; i < size; i++) {
list.add(i);
}
System.out.println("memory:"+ RamUsageEstimator.humanSizeOf(list));
}
结果:memory:19.9 MB
public static void main(String[] args) {
List<Integer> list = new LinkedList<>();
int size = 100 * 10000;
for (int i = 0; i < size; i++) {
list.add(i);
}
System.out.println("memory:"+ RamUsageEstimator.humanSizeOf(list));
}
结果:memory:38.1 MB
同等size大小的集合,显然LinkedList要比ArrayList更吃内存,所以如果是超大集合存储的话,要留意内存使用情况。
二、源码分析
2.1、ArrayList源码
2.1.1、add()
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
ArrayList的add()方法本质就是数组中设置值,这没什么好说的,重点在于扩容,当数组size达到扩容标准后,就会触发System.arraycopy进行数组复制,这对时间和空间上都会有一定的影响。
2.1.2、remove()
// 坐标删除
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
// 对象删除
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
ArrayList的remove如果是对象删除的话也是需要循环的,删除之后还需要再执行一次System.arraycopy复制操作,所以在时间和空间上面效率都很差。如果是坐标删除的话,就不需要进行循环,直接System.arraycopy即可。
2.2、LinkedList原理
2.2.1、add()
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
LinkedList的add()方法没什么可介绍的,就是链表节点的添加。
2.2.2、remove()
// 坐标删除
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
// 对象删除
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
LinkedList不管是坐标删除还是对象删除,第一步都是循环找到需要删除的节点,然后删除该节点的指向,不同的是坐标删除分为前后两种循环,所以链表从头删除和从尾删除,时间效率都是一样的。
总结
介绍到这边,想必童靴们也知道他们之间的区别了,总结一下就是,如果高频添加后删除,建议使用LinkedList,如果主要是查询,那最好使用ArrayList。 使用ArrayList如果能设置数组初始值,最好设置初始值,使用LinkedList删除,最好从头或者从尾开始坐标删除,如果是对象删除,最好从头部开始删除。
- Linux主机之间ssh免密登录配置
- 远控木马Posion Ivy开始肆虐缅甸和其它亚洲国家
- Slf4j+Logback配置文件变量使用小记
- Storm消息处理可靠性保证
- git+github创建分支&提交并贡献代码(linux环境)
- 使用Nginx代理restful实现SSL链路加密
- 使用Nginx代理thrift NIO实现SSL链路加密
- TThreadedSelectorServer介绍及Direct Memory OOM分析
- 通过Java程序提交通用Mapreduce任务并获取Job信息
- Mapreduce 任务提交源码分析1
- Java分布式神经网络库Deeplearning4j 环境搭建和运行一个例子
- Java分布式神经网络库Deeplearning4j之上手实践手写数字图像识别与模型训练
- 调用腾讯优图开放平台进行人脸识别-Java调用API实现
- Spring的事务传播行为
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 构建一个适合stm32mp157系列开发板的嵌入式Linux系统
- linux 达梦数据库 命令行 卸载
- Access Control: Database(数据库访问控制)最新解析及完整解决方案
- 启动Apache Atlas时报错
- Apache Atlas 安装部署
- SwiftUI:禁止用户交互
- Qt音视频开发34-Onvif时间设置
- 网络工程师提高篇 | 路由重发布你了解多少?从原理到配置,瑞哥带你学习一波!
- 短视频APP开发,简单计时功能
- LeetCode | 94.二叉树的中序遍历
- Druid 的整合
- LeetCode | 104.二叉树的最大深度
- Flutter 目录结构和项目资源
- iOS音视频接入- TRTC互动直播
- 【一天一大 lee】查找常用字符 (难度:简单) - Day20201014