ArrayList底层是动态数组,核心在于数组连续、容量可变、懒加载和fail-fast机制。

ArrayList 底层原理面试核心要点,一句话就能锚定:它是用动态数组实现的、支持随机访问的线性表,关键在“数组连续 + 容量可变 + 懒加载 + fail-fast”这四点。
底层结构与关键字段
必须能脱口而出三个核心成员变量:
- transient Object[] elementData:真正存数据的底层数组,不是泛型数组(擦除后是Object[]),且被 transient 修饰——不参与默认序列化;
- private int size:当前已存放的有效元素个数,≠ 数组长度,get(i) 和 size() 返回的是这个值;
- protected transient int modCount:结构性修改计数器,所有 add/remove/clear 等操作都会递增它,是 fail-fast 的判断依据。
还要知道两个空数组常量的区别:DEFAULTCAPACITY_EMPTY_ELEMENTDATA(无参构造初始指向它,表示“待扩容状态”)和 EMPTY_ELEMENTDATA(显式传 0 容量时使用)。
初始化与扩容机制
重点不是背数字,而是讲清设计逻辑:
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
- 无参构造
new ArrayList()不分配数组,只设elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,首次add才扩容到默认容量 10(懒加载省内存); - 扩容触发条件是
size + 1 > elementData.length,即插入前发现“没位置了”; - 新容量计算是
oldCapacity + (oldCapacity >> 1),等价于 1.5 倍,不是 2 倍——既避免频繁扩容(比 1 倍好),又防止过度浪费空间(比 2 倍保守); - 扩容本质是
Arrays.copyOf(elementData, newCapacity),一次 O(n) 的数组复制,但均摊下来 add 是 O(1)。
核心操作时间复杂度与实现细节
不能只说“查快删慢”,要说出为什么、怎么做的:
-
get(int index):直接
elementData[index],O(1),靠内存连续+地址偏移实现; - add(E e):尾插,先 ensureCapacity,再赋值 + size++,均摊 O(1);
-
add(int index, E e):先校验索引,再用
System.arraycopy把 index 后所有元素右移一位,O(n); - remove(int index):同样 arraycopy 左移覆盖,最后把末尾置为 null 辅助 GC,O(n);
-
迭代器遍历时并发修改:Iterator 创建时记录
expectedModCount = modCount,每次 next() 都检查是否一致,不一致就抛ConcurrentModificationException。
常见误区与对比延伸
面试官常借机考察理解深度:
- “ArrayList 线程安全吗?”——不安全,modCount 和数组操作都非原子,多线程下可能数据丢失或异常;
- “和 LinkedList 怎么选?”——查多改少用 ArrayList(连续内存、缓存友好);频繁头/尾插入删除、不关心随机访问用 LinkedList(链表指针操作 O(1));
- “subList 是什么?”——返回原 ArrayList 的视图(共享 elementData),不是新集合,修改 subList 会影响原 list;
- “手动调用 ensureCapacity 有用吗?”——有,预估数据量大时提前扩容,避免多次复制,属于性能优化手段。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










