Java集合框架深入解析:ArrayList & LinkedList
本文解析 Java 中 ArrayList 和 LinkedList 核心差异:ArrayList 基于动态数组,1.5 倍扩容、随机访问快(O (1))但增删慢,有 Fail-Fast 机制;LinkedList 基于双向链表,无需扩容,头尾增删快(O (1))但随机访问慢(O (n)),内存开销更高。选型优先 ArrayList,仅高频头尾操作 / 队列场景选 LinkedList。带有诸多代
Java集合框架深入解析:ArrayList & LinkedList
一、 ArrayList 深度解析
核心定义
ArrayList 是基于动态数组实现的有序集合,核心特点:
- 数组是 “定长” 的,但 ArrayList 通过「扩容机制」实现 “动态”;
- JDK8 中无参构造的 ArrayList,初始化时底层数组是空数组(
DEFAULTCAPACITY_EMPTY_ELEMENTDATA),首次添加元素时才会扩容为 10; - 数组的优势:随机访问快(通过索引直接定位,时间复杂度 O (1));
- 数组的劣势:增删慢(需要移动元素)、扩容有成本(复制数组)。
1. 底层数组实现机制
1.1 数据结构核心
// ArrayList 核心字段,他和Linkedlist都继承了List<E>接口。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// 实际存储数据的数组
transient Object[] elementData; // 关键:非私有,包内可见
// 当前元素数量
private int size;
// 空数组(优化内存)
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
}
1.2 数组特性深入
内存连续性优势:
// 内存连续性优势
public class MemoryLayoutDemo {
public static void main(String[] args) {
// ArrayList:内存连续
// 内存地址:0x1000 [元素1], 0x1004 [元素2], 0x1008 [元素3]
// CPU缓存一次加载连续内存块(通常是64字节)
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add(i);
}
// 访问时:CPU预取后续元素到缓存
// 第一次访问 index=0 时,可能把 0-15 都加载到缓存
// 后续访问 index=1~15 时命中缓存,速度极快
// 对比 LinkedList:内存分散,缓存不友好
}
}
2. 扩容机制深度剖析
2.1 扩容流程详解
// 完整的扩容过程
private void grow(int minCapacity) {
// 1. 记录旧容量
int oldCapacity = elementData.length;
// 2. 计算新容量(核心:1.5倍)
int newCapacity = oldCapacity + (oldCapacity >> 1); // 位运算:右移1位=除以2
// 3. 特殊情况处理
if (newCapacity - minCapacity < 0) // 如果1.5倍还不够
newCapacity = minCapacity; // 直接使用所需容量
// 4. 处理最大容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 5. 数组复制(真正的扩容操作)
elementData = Arrays.copyOf(elementData, newCapacity);
}
扩容触发条件:当添加元素时,数组的实际元素个数(size)等于数组容量(elementData.length),触发扩容。
oldCapacity >> 1 相当于 “位级写法”的 oldCapacity / 2(效率更高),但有三点要注意:
- 只向下取整(舍去小数部分),不会四舍五入。
- 运算对象必须是 整型(
byte/short/int/long)。 - 负数也成立,-7 >> 1 得到 -4,结果仍向负无穷取整。
所以看到
int newCapacity = oldCapacity + (oldCapacity >> 1);
就是
新容量 = 原容量 + 原容量 / 2 ≈ 1.5 倍
这是 ArrayList 扩容时“平滑 1.5 倍”的写法,比直接 / 2 快一点点,习惯上炫技 ,哈哈。
2.2 扩容过程可视化
初始:容量=10,size=0
添加过程:
1. 添加第1个元素:容量10,size=1
2. 添加第11个元素时触发扩容:
oldCapacity = 10
newCapacity = 10 + (10 >> 1) = 10 + 5 = 15
→ 扩容到15,复制原数组
3. 添加第16个元素时再次扩容:
oldCapacity = 15
newCapacity = 15 + (15 >> 1) = 15 + 7 = 22
→ 扩容到22,复制原数组
容量增长序列:10 → 15 → 22 → 33 → 49 → 73 → 109 → ...
2.3 扩容性能影响
public class ExpansionCostDemo {
public static void main(String[] args) {
// 测试扩容开销
int testSize = 10000000;
// 情况1:不指定容量(频繁扩容)
long start1 = System.nanoTime();
List<Integer> list1 = new ArrayList<>();
for (int i = 0; i < testSize; i++) {
list1.add(i);
}
long end1 = System.nanoTime();
// 情况2:指定容量(避免扩容)
long start2 = System.nanoTime();
List<Integer> list2 = new ArrayList<>(testSize);
for (int i = 0; i < testSize; i++) {
list2.add(i);
}
long end2 = System.nanoTime();
System.out.printf("未预分配:%.2f ms\n", (end1 - start1) / 1_000_000.0);
System.out.printf("预分配:%.2f ms\n", (end2 - start2) / 1_000_000.0);
// 结果可能相差数倍!因为数组复制开销很大
}
}
3. Fail-Fast 机制深度解析
核心定义
fail-fast 是 Java 集合(如 ArrayList、LinkedList、HashMap 等)的错误检测机制,是迭代器的错误检测机制,不是集合的 “功能”,目的是避免数据不一致,且不保证 100% 触发(仅作快速失败提示)。
迭代器遍历 ArrayList 时,若检测到集合被「并发修改」(比如遍历中 add/remove),立即抛出 ConcurrentModificationException,这是一种 “快速失败” 的检测机制,而非线程安全机制。
3.1 机制原理
- ArrayList 有个成员变量
modCount:记录集合被修改的次数(调用集合的add/remove等方法 都会让modCount++); - 迭代器创建时,会记录当前的
modCount到expectedModCount; - 遍历过程中,每次
next()都会检查modCount == expectedModCount,不相等则抛异常。
// modCount 机制
public class FailFastDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
// 创建迭代器时记录 expectedModCount = modCount
Iterator<String> iterator = list.iterator();
// 迭代过程中修改列表
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
if (item.equals("B")) {
// 这里修改列表,modCount++
list.remove(item); // 触发 modCount++(因为这里用的是列表的remove函数,不是迭代器的,此时迭代器会认为外部有人恶意篡改数据,导致脏读。如果用迭代器里的remove函数就不会报错。)
// iterator.next() 会检查:
// if (modCount != expectedModCount)
// throw new ConcurrentModificationException();
}
}
}
}
modCount= 列表被结构改动的总次数(ArrayList 的“版本号”)expectedModCount= 迭代器诞生那一刻拍下来的快照(迭代器眼里的“当前版本号”)
3.2 源码实现细节
// ArrayList.Itr 迭代器实现
private class Itr implements Iterator<E> {
int cursor; // 下一个返回元素的索引
int lastRet = -1; // 上一个返回元素的索引
int expectedModCount = modCount; // 关键!记录创建时的modCount(相当于拍照,为了比较列表是否被动过。)
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification(); // 每次next都检查
// ... 其他逻辑
}
public void remove() {
// 迭代器自己的remove会同步modCount
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; // 更新expectedModCount
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
3.3 Fail-Fast 应用场景
public class ConcurrentModificationCase {
// 场景1:单线程错误用法
public void wrongSingleThread() {
//在初始化ArrayList的时候就存入数组{"A","B","C","D"}
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C","D"));
for (String s : list) { // 内部使用迭代器
if (s.equals("B")) {
list.remove(s); //会报错,用的是ArrayList自己的remove方法,抛出ConcurrentModificationException
}
}
}
// 场景2:多线程并发修改
public void multiThreadIssue() {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Thread t1 = new Thread(() -> {
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
});
Thread t2 = new Thread(() -> {
try { Thread.sleep(50); } catch (InterruptedException e) {}
list.add("D"); // 在迭代过程中修改,抛出ConcurrentModificationException
});
t1.start();
t2.start();
}
// 正确做法
public void correctApproach() {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
// 方法1:使用迭代器的remove方法
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("B")) {
iterator.remove(); // 正确!会更新expectedModCount
}
}
// 方法2:使用CopyOnWriteArrayList(并发安全)
// List<String> safeList = new CopyOnWriteArrayList<>(list);
// 方法3:使用并发集合
// List<String> concurrentList = Collections.synchronizedList(list);
}
}
场景一中注释“内部使用迭代器”指的是:
编译器把那句看似简单的
for (String s : list) { … }
偷偷翻译成了带有 Iterator 对象的经典三件套:
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
…
}
你写的“增强 for 循环”只是语法糖,字节码里跑的就是迭代器。
因此:
- 遍历过程中
list.remove(s)会修改 modCount,而迭代器每次next()都会检查modCount != expectedModCount,不一致就抛ConcurrentModificationException。 - 想安全删除,必须用迭代器自己的删除方法
场景二中线程的操作:
线程一先打印出第一个遍历到的元素,然后休眠50毫秒,,此时线程二几乎也同时休眠了50毫秒。然后线程二会进行增加元素的操作,但此时线程一开始next(),如果此时线程一比线程二稍快,也就是线程二增加元素操作慢了一拍的话,线程一next()时候expectedModCount 仍然等于 modCount,此时不会报错,但是等到下一次next()的时候仍然会抛出异常。
所以,经过上面的分析,总结一句:异常是否抛出取决于“迭代器是否还会再检查一次”
4. ArrayList 面试高频问答
| 问题 | 标准答案(简洁版) |
|---|---|
| ArrayList 底层结构? | JDK8 基于动态数组,无参构造初始为空数组,首次 add 扩容到 10。 |
| ArrayList 扩容倍数? | 1.5 倍(oldCapacity + oldCapacity >> 1)。 |
| ArrayList 为什么查询快、增删慢? | 查询:索引直接定位(O (1));增删:需移动元素(O (n))。 |
| 如何优化 ArrayList 性能? | 创建时指定初始容量,减少扩容次数;避免遍历中修改集合。 |
| fail-fast 是什么?怎么解决? | 遍历中检测集合并发修改抛异常;解决:迭代器 remove ()、CopyOnWriteArrayList。 |
二、 LinkedList 深度解析
1. 双向链表结构详解
LinkedList 是基于双向链表实现的有序集合,核心结构:
1.1 节点结构深入
- 每个节点是一个
Node对象,包含 3 个属性:
// Node 类的完整理解
private static class Node<E> {
E item; // 实际存储的数据
Node<E> next; // 后继指针:指向下一个节点
Node<E> prev; // 前驱指针:指向前一个节点
// 构造函数:创建时建立双向链接
Node(Node<E> prev, E element, Node<E> next) {
this.item = element; // 把自己的数据域赋成传进来的元素
this.next = next; // 把自己的后继指向参数 next
this.prev = prev; // 把自己的前驱指向参数 prev
// 下面两行是反向链接:让相邻的节点也认识自己
if (prev != null) prev.next = this; // 前驱的后继 = 我自己
if (next != null) next.prev = this; // 后继的前驱 = 我自己
}
}
- LinkedList 还维护了两个哨兵节点:
first(头节点)、last(尾节点),无需遍历就能快速定位头尾; - 无固定容量,添加元素只需新建节点、修改指针,无需扩容。
1.2 链表结构可视化
public class LinkedListStructure {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
// 添加元素的过程
list.add("A"); // 创建节点1:[null ← A → null]
list.add("B"); // 创建节点2:[A ← B → null],并更新A.next=B
list.add("C"); // 创建节点3:[B ← C → null],并更新B.next=C
// 最终结构:
// first → [null ← A → B] ↔ [A ← B → C] ↔ [B ← C → null] ← last
// (节点1) (节点2) (节点3)
// 双向遍历能力:
// 正向:A → B → C
// 反向:C → B → A
}
}
2. 核心操作原理
2.1 头部插入分析
// linkFirst 方法深度解析
private void linkFirst(E e) {
final Node<E> f = first; // 1. 保存原头节点
// 2. 创建新节点:prev=null, element=e, next=f(指向原头节点)
final Node<E> newNode = new Node<>(null, e, f);
first = newNode; // 3. 更新first指针
if (f == null) // 4. 原链表为空
last = newNode; // 新节点也是尾节点
else
f.prev = newNode; // 5. 原头节点的prev指向新节点
size++;
modCount++;
}
// 时间复杂度:O(1) 常数时间
// 无论链表多长,操作步骤固定
2.2 中间插入分析
// 在指定位置插入
public void add(int index, E element) {
checkPositionIndex(index);//检查传入的参数index是否在数组索引范围内
if (index == size) // 尾部插入
linkLast(element);
else
linkBefore(element, node(index)); // 关键:先找到节点
}
// node(index):查找节点(耗时操作)
Node<E> node(int 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;
}
}
// 时间复杂度:
// 查找节点:O(n/2) = O(n) 线性时间
// 插入操作:O(1) 常数时间
// 总复杂度:O(n)
还有尾部插入,指定的节点前插入…这些我都不一一讲述,有兴趣的小伙伴可以去读源码,读源码是一个很方便快捷的方式,对我们提升自身能力有很大帮助!
其实原理很简单,只要你能理解链表的结构,同时关照到每个方面,源码你也能手搓出来!嘻嘻。
3. 适用场景深度分析
场景1:频繁的头部操作
public class StackImplementation {
// LinkedList 实现栈(后进先出)
public class Stack<E> {
private LinkedList<E> list = new LinkedList<>();
public void push(E item) {
list.addFirst(item); // O(1)
}
public E pop() {
return list.removeFirst(); // O(1)
}
public E peek() {
return list.getFirst(); // O(1)
}
}
// 对比 ArrayList 实现栈
public class ArrayListStack<E> {
private ArrayList<E> list = new ArrayList<>();
public void push(E item) {
list.add(0, item); // O(n)!需要移动所有元素
}
public E pop() {
return list.remove(0); // O(n)!
}
}
}
场景2:实现队列和双端队列
public class QueueDequeDemo {
public static void main(String[] args) {
// LinkedList 实现了 Deque 接口
Deque<String> deque = new LinkedList<>();
// 作为队列使用(FIFO)
deque.offer("A"); // 入队
deque.offer("B");
System.out.println(deque.poll()); // 出队:A
// 作为双端队列使用
deque.offerFirst("C"); // 头部入队
deque.offerLast("D"); // 尾部入队
System.out.println(deque.pollFirst()); // C
System.out.println(deque.pollLast()); // D
// 作为栈使用
deque.push("E"); // 压栈
deque.push("F");
System.out.println(deque.pop()); // 弹栈:F
}
}
场景3:LRU缓存实现
public class LRUCache<K, V> {
private final int capacity;
private final Map<K, Node> cache;
private final LinkedList<Node> list;
class Node {
K key;
V value;
}
public LRUCache(int capacity) {
this.capacity = capacity;
this.cache = new HashMap<>();
this.list = new LinkedList<>();
}
public V get(K key) {
if (!cache.containsKey(key)) return null;
Node node = cache.get(key);
// 移动到头部(最近使用)
list.remove(node); // O(1) 知道节点位置
list.addFirst(node); // O(1)
return node.value;
}
public void put(K key, V value) {
if (cache.containsKey(key)) {
Node node = cache.get(key);
node.value = value;
list.remove(node);
list.addFirst(node);
} else {
if (cache.size() >= capacity) {
Node last = list.removeLast(); // 移除最久未使用
cache.remove(last.key);
}
Node newNode = new Node();
newNode.key = key;
newNode.value = value;
list.addFirst(newNode);
cache.put(key, newNode);
}
}
}
场景4:大数据集的中间位置频繁修改
public class TextEditor {
// 模拟文本编辑器:光标位置频繁插入删除
private LinkedList<Character> text = new LinkedList<>();
private ListIterator<Character> cursor;
public TextEditor() {
cursor = text.listIterator();
}
// 插入字符
public void insertChar(char c) {
cursor.add(c); // O(1) 在迭代器位置插入
}
// 删除字符
public void deleteChar() {
if (cursor.hasPrevious()) {
cursor.previous();
cursor.remove(); // O(1) 删除当前字符
}
}
// 移动光标
public void moveCursorForward() {
if (cursor.hasNext()) cursor.next(); // O(1)
}
public void moveCursorBackward() {
if (cursor.hasPrevious()) cursor.previous(); // O(1)
}
}
4. LinkedList 面试高频问答
| 问题 | 标准答案 |
|---|---|
| LinkedList 底层结构? | 双向链表,每个节点包含 prev/next/item,维护头尾节点,无需扩容。 |
| LinkedList 和 ArrayList 的区别? | 1. 结构:数组 vs 双向链表;2. 访问:ArrayList 快(O (1)),LinkedList 慢(O (n));3. 增删:LinkedList 头尾增删快(O (1)),中间增删仍慢。 |
| LinkedList 可以做队列 / 栈吗? | 可以:队列(addFirst/removeLast)、栈(push/pop,底层是 addFirst/removeFirst)。 |
| LinkedList 为什么不适合随机访问? | 无索引,需从头 / 尾遍历到目标位置,时间复杂度 O (n)。 |
5. 性能对比实战
import java.util.ArrayList;
import java.util.LinkedList;
/**
* ArrayList vs LinkedList 性能对比
* 测试维度:头部插入、尾部插入、随机访问、内存占用
* 结论记忆:
* 1. 头插:LinkedList 完胜(O(1) vs O(n))
* 2. 尾插:两者 O(1) 接近,ArrayList 略快(连续内存 + 扩容公式 1.5 倍)
* 3. 随机读:ArrayList 碾压(O(1) vs O(n))
* 4. 内存:LinkedList 额外 50% ~ 100%(每个 Node 多两个引用域)
*/
public class PerformanceComparison {
// 测试规模:10 万元素(可自行调大/调小)
private static final int SIZE = 100_000;
public static void main(String[] args) {
System.out.println("=== 头部插入性能测试 ===");
testAddFirst(SIZE);
System.out.println("\n=== 尾部插入性能测试 ===");
testAddLast(SIZE);
System.out.println("\n=== 随机访问性能测试 ===");
testRandomAccess(SIZE);
System.out.println("\n=== 内存占用测试 ===");
testMemoryUsage(SIZE);
}
/* ---------- 1. 头部插入 ---------- */
private static void testAddFirst(int size) {
// LinkedList:linkFirst 操作,纯指针调整,O(1)
LinkedList<Integer> linkedList = new LinkedList<>();
long start = System.nanoTime();
for (int i = 0; i < size; i++) {
linkedList.addFirst(i);
}
long linkedTime = System.nanoTime() - start;
// ArrayList:每次 add(0, i) 都要整体右移,O(n)
ArrayList<Integer> arrayList = new ArrayList<>();
start = System.nanoTime();
for (int i = 0; i < size; i++) {
arrayList.add(0, i); // 灾难性性能
}
long arrayTime = System.nanoTime() - start;
printReport("LinkedList", linkedTime, "ArrayList", arrayTime);
}
/* ---------- 2. 尾部插入 ---------- */
private static void testAddLast(int size) {
// LinkedList:linkLast 操作,O(1)
LinkedList<Integer> linkedList = new LinkedList<>();
long start = System.nanoTime();
for (int i = 0; i < size; i++) {
linkedList.addLast(i);
}
long linkedTime = System.nanoTime() - start;
// ArrayList:默认 append,平均 O(1)(偶尔扩容)
ArrayList<Integer> arrayList = new ArrayList<>();
start = System.nanoTime();
for (int i = 0; i < size; i++) {
arrayList.add(i); // 等价 addLast
}
long arrayTime = System.nanoTime() - start;
printReport("LinkedList", linkedTime, "ArrayList", arrayTime);
}
/* ---------- 3. 随机访问 ---------- */
private static void testRandomAccess(int size) {
// 预填充数据,避免插入耗时干扰
LinkedList<Integer> linkedList = new LinkedList<>();
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < size; i++) {
linkedList.add(i);
arrayList.add(i);
}
// 生成同一批随机下标,保证公平
int[] indices = new int[size];
java.util.Random r = new java.util.Random(0);
for (int i = 0; i < size; i++) indices[i] = r.nextInt(size);
// LinkedList:每次 get 都要从最近端遍历,平均 O(n)
long start = System.nanoTime();
long sum = 0; // 防止 JIT 把循环优化掉
for (int idx : indices) sum += linkedList.get(idx);
long linkedTime = System.nanoTime() - start;
// ArrayList:物理下标,直接内存偏移,O(1)
start = System.nanoTime();
sum = 0;
for (int idx : indices) sum += arrayList.get(idx);
long arrayTime = System.nanoTime() - start;
printReport("LinkedList", linkedTime, "ArrayList", arrayTime);
}
/* ---------- 4. 内存占用估算 ---------- */
private static void testMemoryUsage(int size) {
Runtime rt = Runtime.getRuntime();
rt.gc(); // 建议 GC,减少干扰
long before = rt.totalMemory() - rt.freeMemory();
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < size; i++) linkedList.add(i);
rt.gc();
long after = rt.totalMemory() - rt.freeMemory();
double linkedMB = (after - before) / 1024.0 / 1024.0;
// ArrayList 测试
rt.gc();
before = rt.totalMemory() - rt.freeMemory();
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < size; i++) arrayList.add(i);
rt.gc();
after = rt.totalMemory() - rt.freeMemory();
double arrayMB = (after - before) / 1024.0 / 1024.0;
System.out.printf("LinkedList 占用: %.2f MB\n", linkedMB);
System.out.printf("ArrayList 占用: %.2f MB\n", arrayMB);
System.out.printf("LinkedList 多用了 %.1f%% 内存\n", (linkedMB - arrayMB) / arrayMB * 100);
}
/* ---------- 工具:统一打印格式 ---------- */
private static void printReport(String fastName, long fastTime, String slowName, long slowTime) {
System.out.printf("%s: %.2f ms\n", fastName, fastTime / 1_000_000.0);
System.out.printf("%s: %.2f ms\n", slowName, slowTime / 1_000_000.0);
System.out.printf("%s 是 %s 的 %.2f 倍快\n",
fastName, slowName, (double) slowTime / fastTime);
}
}
三、选择决策树
需要频繁随机访问吗?
├── 是 → 选择 ArrayList
└── 否 → 需要频繁在头部插入/删除吗?
├── 是 → 选择 LinkedList
└── 否 → 数据量小吗?
├── 是 → 两者都可,ArrayList 通常更好
└── 否 → 需要实现队列/双端队列吗?
├── 是 → 选择 LinkedList(实现 Deque)
└── 否 → 内存敏感吗?
├── 是 → 选择 ArrayList
└── 否 → 根据具体操作模式选择
四、总结要点
ArrayList 关键点
- 数组实现:内存连续,缓存友好
- 1.5倍扩容:复制数组开销大,应预估容量
- Fail-Fast:快速失败机制,防止并发修改异常
- 读快写慢:随机访问O(1),插入删除O(n)
- 内存效率:只存储数据,无额外指针开销
LinkedList 关键点
- 双向链表:内存分散,每个节点有前后指针
- 操作灵活:头部操作O(1),支持双向遍历
- 适用场景:队列/栈实现、LRU缓存、频繁头部操作
- 内存开销:每个节点多16-24字节额外开销
- 迭代器优势:支持在迭代中高效修改
实际应用建议
- 80% 场景用 ArrayList:大多数应用是读多写少
- 特殊场景用 LinkedList:明确需要队列/栈特性
- 性能关键时实测:用真实数据测试,理论可能不符合实际
- 考虑并发场景:两者都不是线程安全的,需要同步
更多推荐



所有评论(0)