970 字
约 3 分钟
4
S04-J01 · List 与迭代器:ArrayList/LinkedList/ArrayDeque 源码

适用读者:被 ConcurrentModificationException 和 subList 坑过的人。
前置:S04-B08、S03-B02(数组 vs 链表)。
实测环境:OpenJDK 21.0.12.1(2026-11-05)。
覆盖旧文:A-1113(383) A-1112(292) A-1671(76) A-1664(117) A-1675(104) A-1667(93) = 1,065 点。
覆盖扩展:E-S04-011(fail-fast 与 modCount)。
上一篇:S04-X01 · 八股速查与跨系列索引
下一篇:S04-J02 · HashMap 源码。


1. 一句话结论

ArrayList 的"快"来自连续内存 + 摊还 O(1) 的尾部追加;迭代器的"严格"来自 modCount 校验——它宁可抛异常,也不让你在遍历中偷偷改动。

实测三条:

1. foreach 删除 → ConcurrentModificationException
2. Iterator.remove() → [1, 2, 4, 5]
3. subList.clear() 后原列表 → [1, 4, 5]

2. ArrayList 的三个关键事实

事实 说明
底层是 Object[] 连续内存,随机访问 O(1)
扩容 1.5 倍 newCap = oldCap + (oldCap >> 1)
尾部追加摊还 O(1) 扩容那次是 O(n),均摊到 n 次是 O(1)

"默认容量 10"是个常见误解:new ArrayList<>() 初始是空数组,第一次 add 才分配 10。

操作 复杂度 备注
get(i) O(1) 下标直算
add(e) 摊还 O(1) 可能扩容
add(0, e) O(n) 全体右移(S03-B02 实测慢 23.7×)
remove(i) O(n) 左移
contains O(n) 线性扫描

3. 迭代器与 fail-fast(E-S04-011)

实测 1:for (Integer i : list) { if (i == 3) list.remove(i); } → CME。

原因:ArrayList.Itr 在每次 next() 时检查 modCount != expectedModCount,而 list.remove 会改 modCount。

做法 结果
foreach + list.remove ❌ CME
Iterator.remove() ✅ 安全(它同步更新 expectedModCount)
list.removeIf(...) ✅ 最简洁(Java 8+)
多线程修改 ❌ 不是 fail-fast 能解决的(要并发容器,S05)

fail-fast 不是"安全保证":它是尽力检测,不保证一定抛。不要用它做正确性依赖。


4. subList:一个"视图",不是副本(实测 3)

List<Integer> l3 = new ArrayList<>(Arrays.asList(1,2,3,4,5));
List<Integer> sub = l3.subList(1,3);
sub.clear();
// 实测:l3 → [1, 4, 5]
事实 后果
subList 返回视图(内部持有原列表引用 + offset) 改视图 = 改原列表
原列表结构改动后,视图操作会抛 CME 不能"拿着视图不放"
要独立副本 new ArrayList<>(list.subList(a,b))

5. LinkedList 与 ArrayDeque:什么时候用哪个

结构 头插/尾插 随机访问 内存
ArrayList 尾 O(1),头 O(n) O(1) 紧凑
LinkedList O(1) O(n) 每节点 2 指针 + 对象头
ArrayDeque 两端 O(1) 不支持 环形数组,紧凑

实测结论(S03-B02/B03):ArrayDeque 做栈/队列比 LinkedList 快 2.8×。LinkedList 只在"频繁中间插入且已持有节点"或 LRU 场景有用。


6. 五个误区

误区 真相
"ArrayList 默认容量 10" 空数组,首次 add 才分配
"foreach 里能删元素" 抛 CME(除 removeIf/Iterator)
"fail-fast 保证线程安全" 只是尽力检测
"subList 是副本" 是视图
"LinkedList 更适合做队列" ArrayDeque 更快更省

7. 自检

  1. ArrayList 扩容倍数是多少?为什么尾插是摊还 O(1)?
  2. CME 由什么机制触发?哪两种写法可以安全删除?
  3. subList.clear() 会影响原列表吗?为什么?
  4. 为什么 fail-fast 不能当"线程安全"用?
  5. 栈/队列该选 LinkedList 还是 ArrayDeque?

8. 下一篇

S04-J02 · HashMap 源码:哈希、扩容、树化与线程不安全

下一篇用实测的 table 长度变化(16 → 32 → 64)讲清扩容时机,并解释"容量为什么必须是 2 的幂"。


本篇属于系列《S04 · Java 语言核心》。旧文来源:A-1113/1112/1671/1664/1675/1667;扩展点 E-S04-011。三条行为为 OpenJDK 21 实测。

S04-J01 · List 与迭代器:ArrayList/LinkedList/ArrayDeque 源码
https://www.clxhxhhr.top/posts/5224/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。