ArrayList 和 LinkedList 之间应该怎么选择

这篇文章是来自知乎上的一个问题

相信很多人在面试时都被问过这个问题,然后一般回答:ArrayList在指定下标访问时快,LinkedList在插入/删除元素时快。

其实这是一种人云亦云的谬误。可能最初有人这么回答,然后不加验证地转来转去,后面就成了八股文中的“标准答案”。

这个问题其实早在2015年,就由作者Joshua Bloch在Twitter上给出了回答:“有人真的使用过LinkedList吗?我写了它,但是我从没用过”。

请添加图片描述

以下是我在知乎上的回答

——————————————————————————————————————————————————————————

先说结论:需要List的场合,绝大部分情况下选择ArrayList;需要Queue或Stack的场合,绝大部分情况下选择ArrayDeque

看实际的性能测试。对ArrayList和LinkedList分别做100000次以下操作,统计执行时间(ms):

尾部插入头部插入中间插入遍历
ArrayList53461527
LinkedList2450442

逐条分析解释:

  1. 尾部插入:相差不大,都是O(1)的操作。
  2. 头部插入:LinkedList明显优于ArrayList。因为ArrayList每次操作都涉及底层全量数组的System.arraycopy();而LinkedList是双向链表,内存保存了headNode,所以是O(1)的操作。
  3. 中间插入:ArrayList明显优于LinkedList。因为LinkedList每次需要遍历寻找插入节点,仅这一步复杂度就是O(n)。
  4. 遍历:相差不大。

除了上述时间消耗的比较,在空间消耗上ArrayList也优于LinkedList。虽然ArrayList会扩容到1.5倍,但是LinkedList内部为每个存储元素封装了Node对象,除了Node对象头部,还要保存前后Node的地址和当前存储元素的地址,实际上LinkedList占用内存要远大于ArrayList。

结论:仅在头部插入/删除时,LinkedList在时间上有性能优势。

不过问题来了:既然你需要在头部频繁插入/删除,那么为什么不选择更合适的数据结构呢?比如Stack或者Queue。

那么LinkedList是实现这两种数据结构的最优选择吗?答案也不是,而是ArrayDeque。

不多说,再看性能测试对比。对ArrayDeque和LinkedList分别做1000000次以下操作,统计执行时间(ms):

头部插入尾部插入遍历
ArrayList121266
LinkedList6115310

可以看出,ArrayDeque在每项时间指标都优于LinkedList。另外在空间上,ArrayDeque底层使用的循环数组也是优于LinkedList使用的Node链表。

结论:无论在时间还是空间上,ArrayDeque都优于LinkedList。

顺便说一句,JDK中还提供了Stack类。虽然它名字就叫Stack,一般不会用到它实现栈,甚至Java官方都建议不要使用它。因为有以下缺陷:

  1. 继承关系和方法设计混乱。
  2. 为了线程安全所有方法加线程锁,性能较差。

那么综合看来,LinkedList是否一无是处呢?我觉得它的存在还是有一定价值的:

  1. 典型的数据结构。作为一种基础数据结构,链表在几乎每种语言中都有自己的实现。LinkedList作为链表在Java中的实现,起到了教学和理论研究的作用。它在Java中性能不好更多与JVM本身的内存结构有关,并非在所有语言中都是这样。
  2. 历史原因。LinkedList刚出来时还是实现Queue和Stack的最佳选择,不过后面1.6版本推出的ArrayDeque在性能上后来居上,为了版本兼容的考虑LinkedList还是保留了下来。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值