一句话总结
ArrayList 默认初始容量 10(JDK 7+ 延迟初始化),扩容为 1.5 倍(oldCapacity + oldCapacity >> 1),通过 Arrays.copyOf() 复制到新数组。1.5 倍是空间与时间的折中(比 HashMap 的 2 倍更保守)。预知容量时用 new ArrayList<>(n) 或 ensureCapacity() 避免多次扩容。
初级理解
ArrayList 底层是数组,数组长度固定。当元素数量超过当前数组容量时,需要扩容——创建一个更大的新数组,把旧数组的元素复制过去。
扩容规则:
1. 默认初始容量:10(JDK 7+ 是延迟初始化,第一次 add 时才创建数组)
2. 扩容倍数:新容量 = 旧容量 + 旧容量 / 2,即 1.5 倍
3. 扩容方式:通过 Arrays.copyOf() 创建新数组并复制元素
中级深入
为什么是 1.5 倍?这是空间和时间的折中。倍数太小 → 频繁扩容,性能差;倍数太大 → 浪费内存。1.5 倍是经验值,比 HashMap 的 2 倍更保守(因为数组是连续内存,太大可能分配失败)。
JDK 6 vs JDK 7+ 的区别:
JDK 6:构造时直接创建长度为 10 的数组
JDK 7+:构造时赋值为空数组(DEFAULTCAPACITY_EMPTY_ELEMENTDATA),第一次 add 时才创建长度为 10 的数组(延迟初始化,节省内存)
ensureCapacity 方法:如果预知要添加大量元素,可以提前调用 ensureCapacity 一次性扩容到位,避免多次扩容。
高级拓展
扩容的性能影响:每次扩容都需要 Arrays.copyOf(),本质是创建新数组 + System.arraycopy()(native 方法,内存复制)。频繁扩容会导致频繁 GC(旧数组被回收)。
最佳实践:
1. 能预估元素数量时,使用 new ArrayList<>(n) 指定初始容量
2. 大数据量时先调用 ensureCapacity
3. 扩容后旧数组不再使用,可以被 GC 回收
ArrayList 的最大容量:理论上最大为 Integer.MAX_VALUE - 8(约 21 亿),但实际受限于 JVM 可用内存。超过后会尝试扩容到 Integer.MAX_VALUE,如果还不够则抛 OutOfMemoryError。
实战场景
场景:批量插入优化
面试模拟
Q:ArrayList 扩容为什么是 1.5 倍而不是 2 倍?
A:1.5 倍是空间和时间的折中。倍数太小→频繁扩容性能差;倍数太大→浪费内存。数组需要连续内存,2 倍扩容可能导致大数组分配失败。HashMap 用 2 倍是因为它的数组存的是引用(4/8 字节),内存压力小。
Q:JDK 7 的延迟初始化有什么好处?
A:很多 ArrayList 创建后可能从未添加元素(如作为方法返回值、空集合占位)。JDK 6 构造时直接分配 10 长度的数组,浪费内存。JDK 7+ 构造时赋空数组,第一次 add 才分配,节省了大量内存。