ArrayList 扩容机制
面试官问:"ArrayList 的扩容机制是什么?"
候选人小高答:"ArrayList 默认容量是 10,超过后扩容 1.5 倍。"
面试官追问:"为什么是 1.5 倍,不是 2 倍?"
小高说:"可能 1.5 倍更节省内存?"
面试官又问:"如果一次性 add 100 万个元素,怎么初始化最好?"
小高说不上来。
【面试官心理】 这道题看似简单,但追问"1.5 倍的原因"和"批量初始化"能测出候选人对性能优化的理解。能说出"避免频繁扩容"和"newCapacity 计算公式"的候选人,说明看过源码。
一、ArrayList 底层结构 🔴
关键点:elementData 是 Object[],不是泛型数组(因为泛型会被类型擦除)。
二、扩容流程源码解析 🔴
2.1 add 方法
2.2 ensureCapacityInternal
2.3 扩容的核心:grow 方法
2.4 扩容过程图解
三、为什么是 1.5 倍?🔴
3.1 扩容倍数的对比
3.2 数学分析
JVM 的 Arrays.copyOf() 使用 System.arraycopy(),这是 native 方法,在堆内存中复制数据。
- 扩容太小:频繁扩容,CPU 开销大
- 扩容太大:内存浪费
1.5 倍是一个经验最优值:
- 不是 1.0 倍(浪费空间)
- 不是 2.0 倍(内存浪费过大)
- 1.5 倍在时间和空间之间取得了平衡
💡
扩容的真正性能瓶颈是 Arrays.copyOf() 的数据复制。如果预估数据量已知,应该预先调用 ensureCapacity() 一次性扩容到位。
四、扩容的性能问题 🔴
4.1 扩容的代价
4.2 正确的做法
五、无参构造器的秘密 🔴
💡
JDK 7+ 的无参构造器使用懒加载策略,初始不分配内存。这在创建大量可能为空或很少使用的 ArrayList 时节省了内存。
六、生产避坑
6.1 Integer.MAX_VALUE 边界
6.2 扩容导致的 GC 问题
在高频写入场景(如日志收集),ArrayList 频繁扩容会产生大量临时数组,增加 GC 压力。
解决方案:
- 预估容量,一次分配到位
- 使用 LinkedList 避免扩容问题(但访问性能差)
- 使用 RingBuffer 等无锁队列
【面试官心理】 能说出"懒加载"和"扩容次数计算"的候选人,说明真正研究过 ArrayList 源码。这种追问能力是 P6 的标准要求。