# 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

扩容的性能代价:

  • 创建新数组 (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

测试结果(示例):

未指定初始容量:300 ms  
预分配初始容量:150 ms  
1
2
  • 预分配容量后,减少扩容次数,插入效率提升 50%+
  • 避免多次数组复制,降低 CPU 与 GC 开销。

# 常见错误

  1. 误认为 ArrayList 默认不会扩容
    • 默认容量 10,超出后会触发扩容
  2. 过度预分配,浪费内存
    • 如果 initialCapacity 设得过大,可能导致内存浪费,影响 GC。

# 最佳实践

  • **小批量数据(<1000 条):**使用默认构造,避免浪费内存。
  • **大批量数据(>10,000 条):**手动 new ArrayList<>(size) 预分配,提升性能。
  • **动态增长数据集:**使用 ensureCapacity() 避免多次扩容。

# 深入追问

  • ArrayList 为什么扩容是 1.5 倍 而不是 2 倍?
  • ensureCapacity()JVM 层面 是如何优化的?

# 相关面试题

  • ArrayListLinkedList 在批量插入数据时,哪个更快?
  • ArrayList 扩容过程如何影响 GC(垃圾回收)