ArrayList 和 LinkedList 完整区别对比
一、底层数据结构
-
ArrayList
底层:动态 Object 数组 ,内存连续存储。
初始化空数组,第一次 add 扩容到 10,每次扩容 1.5 倍,依靠
System.arraycopy复制数组。 -
LinkedList
底层:双向链表 ,每个节点 Node 包含:数据、前驱指针、后继指针。
内存不连续,没有扩容概念,新增元素只新建节点修改指针。
二、核心性能差异(时间复杂度)
| 操作 | ArrayList | LinkedList |
|---|---|---|
get(index) 随机查询 |
O(1) 极快 | O(n),需要从头/尾遍历到下标 |
| 尾部 add() | O(1)(不扩容) | O(1) |
| 头部/中间 add(index) | O(n),大量数组移位复制 | O(n),仅修改指针,无数据拷贝 |
| 头部/中间 remove(index) | O(n),数组前移覆盖 | O(n),仅修改前后节点指针 |
| 按对象 remove(Object) | O(n) | O(n) |
三、内存占用
- ArrayList:只存元素,内存开销小;数组预留容量会有闲置空间。
- LinkedList:每个元素包装成 Node,额外存两个指针(prev、next),内存开销更大。
四、线程安全
两者都非线程安全 ,多线程并发读写会抛出 ConcurrentModificationException。
同步方案统一:Collections.synchronizedList()。
五、支持的特性
-
ArrayList
实现
RandomAccess标记接口,支持快速随机访问,推荐普通for循环遍历。 -
LinkedList
没有
RandomAccess;额外实现Deque双端队列接口,可当栈、队列、双端队列使用:
java
list.push(); // 栈头插入
list.pop(); // 栈头弹出
list.pollFirst();
list.addLast();
六、遍历效率
- ArrayList:普通 for 循环 > foreach > 迭代器
- LinkedList:禁止用普通for循环 get(i),每次get都会从头遍历,性能极差;只能用 foreach / Iterator。
七、适用场景
优先使用 ArrayList
- 大量查询、根据下标取值;
- 只在尾部增删元素;
- 数据量不大、遍历频繁;
- 需要随机访问。
优先使用 LinkedList
- 频繁在头部、中间插入/删除;
- 需要充当队列、栈、双端队列;
- 极少通过下标查询元素。
八、常见误区
- 误区:LinkedList 所有增删都比 ArrayList 快
纠正:尾部新增两者差不多;中间插入大量数据时,ArrayList 需要数组复制,LinkedList 更快;单纯尾部添加差距很小。 - 误区:LinkedList 不需要扩容所以一定更快
纠正:链表节点创建、指针操作有开销,大量查询场景远慢于数组。 - 误区:for循环遍历LinkedList没问题
纠正:for(int i=0;i<size;i++) list.get(i)时间复杂度 O(n²),数据多会非常卡顿。
九、简短总结背诵版(面试常用)
- 底层:ArrayList 动态数组;LinkedList 双向链表。
- 查询:ArrayList 随机访问快;LinkedList 慢。
- 中间/头部增删:ArrayList 移位复制慢;LinkedList 修改指针快。
- 内存:ArrayList 紧凑;LinkedList 节点指针额外开销。
- 接口:LinkedList 实现Deque,可做栈队列;ArrayList 支持随机访问。
- 使用场景:查询多用ArrayList;频繁头尾插入删除、做队列用LinkedList。