Java ArrayList 扩容机制详解

发布于:2025-02-11 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、触发条件

当向 ArrayList 添加元素时(如 add()addAll() 方法),若当前元素数量(size)已达到数组容量(elementData.length),则会触发扩容。

二、扩容流程

  1. 计算最小容量
    所需最小容量为当前元素数量 size + 1(单个添加)或 size + numNew(批量添加)。

  2. 检查当前容量
    若当前数组容量不足,调用 grow(minCapacity) 方法进行扩容。

  3. 计算新容量

    • 默认策略:新容量为原容量的 1.5 倍(即 oldCapacity + (oldCapacity >> 1))。
    • 特殊情况:若新容量仍不满足最小需求,则直接使用最小容量。
  4. 处理最大容量限制
    若新容量超过 MAX_ARRAY_SIZEInteger.MAX_VALUE - 8),则扩容至 Integer.MAX_VALUE

  5. 数组复制
    使用 Arrays.copyOf() 创建新数组,并将原数据复制到新数组中。


三、源码关键方法

// JDK 17 源码核心逻辑
private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = ArraysSupport.newLength(
            oldCapacity,
            minCapacity - oldCapacity,  // 最小增量
            oldCapacity >> 1            // 默认增量(原容量的一半)
        );
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        // 初始空数组首次扩容
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

四、扩容策略示例

原容量 操作 新容量 计算逻辑
0(初始) 首次添加元素 10 默认初始容量
10 添加第 11 个元素 15 10 + (10 >> 1) = 15
15 添加第 16 个元素 22 15 + 7 = 22(15 >> 1 = 7)
10 批量添加 20 个元素 30 原容量 10+10=20 < 30 → 直接扩容至 30

五、性能优化建议

  1. 预分配容量
    构造时指定初始容量,避免多次扩容:

    // 已知需要存储1000个元素
    List<Integer> list = new ArrayList<>(1000);
    
  2. 批量操作优化
    使用 addAll() 替代循环添加,减少扩容次数:

    // 高效批量添加
    list.addAll(Arrays.asList(1, 2, 3, 4, 5));
    
  3. 内存回收
    对不再扩容的列表调用 trimToSize(),释放多余空间:

    list.trimToSize(); // 将数组容量调整为当前元素数量
    

六、特殊场景处理

  1. 空列表首次扩容
    使用无参构造器时,初始数组为空。首次添加元素时,容量直接设为 10

  2. 超大容量处理
    当尝试扩容超过 Integer.MAX_VALUE - 8 时,可能抛出 OutOfMemoryError


七、与 Vector 的对比

特性 ArrayList Vector
扩容倍数 1.5 倍 2 倍
线程安全 非线程安全 同步方法(线程安全)
性能 更高 较低(锁开销)

总结

ArrayList 的扩容机制通过 1.5 倍动态增长 平衡了内存利用与性能开销。理解其内部实现有助于在开发中:

  • 避免频繁扩容带来的性能损耗
  • 合理预分配容量优化内存使用
  • 选择适合场景的列表实现类(如高并发时使用 CopyOnWriteArrayList