你确定 LinkedList 在新增/删除元素时,效率比 ArrayList 高?
在面试的时候都会被问到集合相关的问题,比如:你能讲讲 ArrayList 和 LinkedList 的区别吗?
那么我相信你肯定能够答上来:ArrayList 是基于数组实现的, LinkedList 是基于链表实现的
接下来面试官就会连环问了,那你能讲讲,它们都用在什么场景下吗?
阿粉知道这种程度肯定难不倒咱们读者的:因为 ArrayList 是基于数组实现的,所以在遍历的时候, ArrayList 的效率是要比 LinkedList 高的, LinkedList 是基于链表实现的,所以在进行新增/删除元素的时候, LinkedList 的效率是要比 ArrayList 高的
面试官:哦哦,好的,我大概了解了,我这边没有什么想问的了,您回去等消息可以吗
???发生了什么?
哈哈,上面模拟了一个面试场景,是想引出来这篇文章的主题:LinkedList 在新增/删除元素时,效率比 ArrayList 高,这是真的吗?
我相信你也知道套路,一般这么一问,那肯定就不是真的了
放一张图片,这是经过我测试之后的真实结果
因为微信不能放外链的缘故,可以在公众号后台发送 “20200821” 获取测试代码
ArrayList 与 LinkedList 新增元素比较
从图中可以看出来, LinkedList 在新增元素时,它的效率不一定比 ArrayList 高,这是要分情况的
如果是从集合头部位置新增元素的话,那确实是 LinkedList 的效率要比 ArrayList 高
但是如果是从集合中间位置或者是尾部位置新增元素, ArrayList 效率反而要比 LinkedList 效率要高
Excuse me ?竟然和我以前学的不一样?阿粉我学的浅,你别骗我
哈哈哈,为什么会这样呢
这是因为 ArrayList 是基于数组实现的嘛,而数组是一块连续的内存空间,所以在添加元素到数组头部时,需要对头部后面的数据进行复制重排,所以效率是蛮低的
但是 LinkedList 是基于链表实现的,在添加元素的时候,首先会通过循环查找到添加元素的位置,如果要添加的位置处于 List 前半段,那就从前向后找;如果位置在后半段,那就从后往前找,所以 LinkedList 添加元素到头部是非常高效的(小声 BB ,这我知道
哦,这你知道?看来基础蛮不错的嘛~
所以当 ArrayList 在添加元素到数组中间时,有一部分数据需要复制重排,效率就不是很高,那为啥 LinkedList 比它还要低呢?这是因为 LinkedList 把元素添加到中间位置的时候,需要在添加之前先遍历查找,这个查找的时间比较耗时
添加元素到尾部操作中, ArrayList 的效率要比 LinkedList 的还要高,这是为啥嘞
因为 ArrayList 在添加的时候不需要什么操作,直接插入就好了,所以效率蛮高的
但是 LinkedList 就不一样了,对于 LinkedList 来说,也不需要查找啥的,直接插入就可以了,但是需要 new 对象,还有变换指针指向对象呀,这些过程耗时加起来可就比 ArrayList 长了
它是有前提的,那就是 ArrayList 初始化容量是足够的情况下,才有上述的特点,如果 ArrayList 涉及到动态扩容,那它的效率肯定会降低
ArrayList 与 LinkedList 删除元素比较
删除元素和新增元素的原理是一样的,所以删除元素的操作和新增元素的操作耗时也是很相近
这里就不再赘述
ArrayList 与 LinkedList 遍历元素比较
测试结果非常明显,对于 LinkedList 来说,如果使用 for 循环的话,效率特别低,但是 ArrayList 使用 for 循环去遍历的话,就比较高
为啥呢?
emmm ,得从源码说起
先来看 ArrayList 的源码吧,这个比较简单
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
能够看到, ArrayList 实现了 List , RandomAccess , Cloneable 还有 Serializable 接口
你是不是对 RandomAccess 这个接口挺陌生的?这是个啥?
但是通过查阅源码能够发现它也只是个空的接口罢了,那 ArrayList 为啥还要去实现它嘞
因为 RandomAccess 接口是一个标志接口,它标识着“只要实现该接口的 list 类,都可以实现快速随机访问”
实现快速随机访问?你能想到什么?这不就是数组的特性嘛!可以直接通过 index 来快速定位 & 读取
那你是不是就能想到, ArrayList 是数组实现的,所以实现了 RandomAccess 接口, LinkedList 是用链表实现的,所以它没有用 RandomAccess 接口实现吧?
beautiful ~就是这样
咱瞅瞅 LinkedList 源码
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
果然,跟咱们设想的一样,没有实现 RandomAccess 接口
那为啥 LinkedList 接口使用 for 循环去遍历的时候,慢的不行呢?
咱们瞅瞅 LinkedList 在 get 元素时,都干了点儿啥
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
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;
}
}
在 get 方法中,主要调用了 node() 方法,因为 LinkedList 是双向链表,所以 if (index < (size >> 1)) 在判断 i 是在前半段还是后半段,如果是前半段就正序遍历,如果是在后半段那就倒序遍历,那么为什么使用 for 循环遍历 LinkedList 时,会这么慢?(好像离真相越来越近了
原因就在两个 for 循环之中,以第一个 for 循环为例
-
get(0) :直接拿到了node0 地址,然后拿到 node0 的数据
-
get(1) :先拿到 node0 地址,然后 i < index ,开始拿 node1 的地址,符合条件,然后去拿 node1 的数据
-
get(2) :先拿到 node0 的地址,然后 i < index ,拿到 node1 的地址, i < index ,继续向下走,拿到 node2 的地址,符合条件,获取 node2 的数据
发现问题了嘛?我就是想要 2 的数据, LinkedList 在遍历时,将 0 和 1 也给遍历了,如果数据量非常大的话,那效率可不就唰唰的下来了嘛
那到现在,咱们也就非常明确了,如果是要遍历 ArrayList 的话,最好是用 for 循环去做,如果要遍历 LinkedList 的话,最好是用迭代器去做
我猜你一定会说,阿粉啊,那如果对方就给我传过来了一个 list ,我不知道它是 ArrayList 还是 LinkedList 呀?我该怎么办呢
还记得 ArrayList 和 LinkedList 有什么不同吗?是不是 ArrayList 实现了 RandomAccess 接口,但是 LinkedList 没有实现,所以可以从这点去着手解决
暖暖的阿粉在这里给个简单的小 demo ,可以参考下:
public class ListTest {
public static void main(String[] args) {
List<String> arrayList = new ArrayList<String>();
arrayList.add("aaa");
arrayList.add("bbb");
isUseIterator(arrayList);
List<String> linkedList = new LinkedList<String>();
linkedList.add("ccc");
linkedList.add("ddd");
isUseIterator(linkedList);
}
public static void isUseIterator(List list){
if (list instanceof RandomAccess){
System.out.println("实现了 RandomAccess 接口,使用 for 循环遍历");
for (int i = 0 ; i < list.size(); i++ ){
System.out.println(list.get(i));
}
}else{
System.out.println("没有实现 RandomAccess 接口,使用迭代器遍历");
Iterator it = list.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
}
}
本篇文章用到的所有代码,都上传到了 github 上,因为微信不能放外链的缘故,可以在公众号后台发送 “20200821” 获取本文完整代码
所以,乖,下次面试官再问你 LinkedList 在新增/删除元素时,效率比 ArrayList 高吗,不要再傻傻的回答是了,拿阿粉这篇文章和他扯皮,保证没问题
参考:
极客时间 — 《Java 性能调优实战》
< END >
如果大家喜欢我们的文章,欢迎大家转发,点击在看让更多的人看到。也欢迎大家热爱技术和学习的朋友加入的我们的知识星球当中,我们共同成长,进步。
本文分享自微信公众号 - Java极客技术(Javageektech)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Docker 架构及工作原理
通过下图可以得知,Docker 在运行时分为 Docker 引擎(服务端守护进程) 和 客户端工具,我们日常使用各种 docker 命令,其实就是在使用 客户端工具 与 Docker 引擎 进行交互。 Client 客户端 Docker 是一个客户端-服务器(C/S)架构程序。Docker 客户端只需要向 Docker 服务器或者守护进程发出请求,服务器或者守护进程将完成所有工作并返回结果。Docker 提供了一个命令行工具 Docker 以及一整套 RESTful API。你可以在同一台宿主机上运行 Docker 守护进程和客户端,也可以从本地的 Docker 客户端连接到运行在另一台宿主机上的远程 Docker 守护进程。 Host 主机(Docker 引擎) 一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。 Image 镜像 什么是 Docker 镜像?简单的理解,Docker 镜像就是一个 Linux 的文件系统(Root FileSystem),这个文件系统里面包含可以运行在 Linux 内核的程序以及相应的数据。 通过镜像启动一个容器,一个镜像就是一...
- 下一篇
2020百度智能计算峰会:发布六大新品、升级四大平台,加大投入AI新基建
百度在AI新基建领域的投入持续加码。 8月20日,ABC SUMMIT 2020百度智能云智能计算峰会举行,大会以“新基建 新计算 新动能”为主题。百度CTO王海峰、百度集团副总裁侯震宇、百度智能云副总经理谢广军等登台演讲AI新基建大潮下的产业智能化新趋势,在业内首次提出各行各业应用进入AI-Native阶段。 大会还重磅发布了自主研发的新一代云基础架构百度“太行”,以及六大新产品并升级四大应用平台,实现智能计算能力全面升级。这将为AI新基建发展注入新动能,加速中国产业智能化升级步伐。 王海峰表示,人工智能已经成为新一轮科技革命和产业变革的核心驱动力量,而人工智能技术的高速发展,依托于算法、算力和数据的快速发展。有了强大的算力才能处理大规模的数据,才能应用更先进的算法。随着产业智能化升级的加速,数据中心要升级,服务器也要升级,这些支撑了算力的升级,进而支撑人工智能技术更快速地应用于各行各业,使我们的社会和生活变得更加美好。这也是我们今天以“新基建、新计算、新动能”为主题的原因。我们希望通过新基建以及我们的新计算,配合百度的人工智能技术,为整个社会发展提供新的动能。 百度CTO王海峰:...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Linux系统CentOS6、CentOS7手动修改IP地址
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- 2048小游戏-低调大师作品
- SpringBoot2配置默认Tomcat设置,开启更多高级功能