arraylist基于连续内存的动态数组实现,扩容虽有开销但维持了内存连续性,使cpu缓存行能批量加载相邻元素,顺序遍历命中率超90%;linkedlist节点分散,缓存命中率不足10%。

ArrayList 的扩容机制本身不直接决定缓存命中率,但它强化了数组连续内存这一关键前提——而正是这种连续布局,让 CPU 缓存能高效工作。
连续内存是缓存友好的物理基础
ArrayList 底层是 Object[] elementData,所有元素在堆内存中占据一块连续地址空间。比如五个元素可能分布在:
0x1000 → A
0x1004 → B
0x1008 → C
0x100C → D
0x1010 → E
这种布局意味着:只要知道首地址和元素大小,任意索引的地址都能用 base + index × size 算出,无需遍历或跳转。
CPU 缓存行加载放大连续访问优势
现代 CPU 缓存不是按单个元素加载,而是按“缓存行”(通常 64 字节)批量从主存读取。当访问 index=0 的元素时:
- CPU 把包含它的整个缓存行(比如覆盖 0x1000–0x103F)载入 L1 缓存
- 紧接着访问 index=1、2、3… 时,大概率已在缓存中,直接命中
- 一次主存访问可服务后续多次元素读取,遍历效率极高
实测中,ArrayList 顺序遍历的缓存命中率常达 90% 以上。
扩容如何间接巩固这一优势
虽然扩容要复制数据、申请新连续块,看似有开销,但它始终维持“单一连续数组”的结构不变:
- 旧数组被丢弃,新数组仍是完整连续块,不碎片化
- 1.5 倍扩容策略平衡了空间利用率和扩容频次,避免频繁小步扩容导致缓存行反复失效
- 批量添加(如 addAll)会预估总容量并一次性扩容到位,减少中间态非最优布局
反观 LinkedList,节点分散在不同内存页,每次访问新节点都可能触发一次缓存未命中,命中率往往低于 10%。
写代码时可感知的实践影响
这些底层特性会真实反映在性能选择上:
- 遍历多、查多改少 → 优先 ArrayList(靠连续内存吃缓存红利)
- 频繁在头部插入/删除 → LinkedList 理论占优,但实际因缓存劣势,往往更慢
- 已知数据量(如读取万级日志),用 new ArrayList(estimatedSize) 避免扩容,让连续性从一开始就是最优的
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











