# 13. 如何使用 ArrayList 预分配空间来提升性能?
# 标准答案
ArrayList
通过构造方法 new ArrayList<>(initialCapacity)
预先分配底层数组的空间,可以减少动态扩容的次数,提高插入性能。默认情况下 ArrayList
初始容量为 10
,扩容采用 1.5 倍扩展策略,每次扩容会触发数组复制,导致性能损耗。合理设置初始容量 initialCapacity
可以减少扩容开销,提高性能。
# 答案解析
# 1. 为什么 ArrayList 需要扩容?
ArrayList
底层使用 动态数组 存储元素,数组的长度是固定的,当元素数量超过数组容量时,必须创建新数组并复制旧数据。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容 1.5 倍
elementData = Arrays.copyOf(elementData, newCapacity);
}
1
2
3
4
5
2
3
4
5
扩容的性能代价:
- 创建新数组 (
O(1)
) - 数据复制 (
O(n)
) - GC 触发(旧数组可能进入垃圾回收)
如果不提前分配足够空间,频繁扩容会导致性能抖动,尤其是在批量插入场景中。
# 2. 如何预分配空间减少扩容?
方法 1:使用构造方法指定初始容量
List<Integer> list = new ArrayList<>(1000);
1
- 适用于数据量可预估的场景,例如读取数据库数据、日志批量处理等。
- 避免动态扩容,提升插入性能。
方法 2:使用 ensureCapacity
list.ensureCapacity(5000);
1
ensureCapacity(int minCapacity)
可以手动扩容,而不会影响现有数据。- 适用于动态增长的场景,如批量处理前先扩容,提高插入性能。
# 3. 预分配容量的性能提升对比
public class ArrayListTest {
public static void main(String[] args) {
int size = 10_000_000;
// 方式 1:不指定初始容量
long start1 = System.nanoTime();
List<Integer> list1 = new ArrayList<>();
for (int i = 0; i < size; i++) {
list1.add(i);
}
long end1 = System.nanoTime();
System.out.println("未指定初始容量:" + (end1 - start1) / 1_000_000 + " ms");
// 方式 2:预分配容量
long start2 = System.nanoTime();
List<Integer> list2 = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
list2.add(i);
}
long end2 = System.nanoTime();
System.out.println("预分配初始容量:" + (end2 - start2) / 1_000_000 + " ms");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
测试结果(示例):
未指定初始容量:300 ms
预分配初始容量:150 ms
1
2
2
- 预分配容量后,减少扩容次数,插入效率提升 50%+
- 避免多次数组复制,降低 CPU 与 GC 开销。
# 常见错误
- 误认为 ArrayList 默认不会扩容
- 默认容量
10
,超出后会触发扩容。
- 默认容量
- 过度预分配,浪费内存
- 如果
initialCapacity
设得过大,可能导致内存浪费,影响 GC。
- 如果
# 最佳实践
- **小批量数据(<1000 条):**使用默认构造,避免浪费内存。
- **大批量数据(>10,000 条):**手动
new ArrayList<>(size)
预分配,提升性能。 - **动态增长数据集:**使用
ensureCapacity()
避免多次扩容。
# 深入追问
ArrayList
为什么扩容是 1.5 倍 而不是 2 倍?ensureCapacity()
在 JVM 层面 是如何优化的?
# 相关面试题
ArrayList
和LinkedList
在批量插入数据时,哪个更快?ArrayList
扩容过程如何影响 GC(垃圾回收)?