深入浅出 ArrayList:从基础用法到底层原理的全面解析(下)

-1

五、ArrayList 底层核心原理 —— 扩容机制与性能优化

很多开发者使用 ArrayList 时,只知道 “它能自动扩容”,但不清楚扩容的具体逻辑 —— 比如 “什么时候扩容?”“扩容到多大?”“扩容过程消耗性能吗?”。理解这些底层原理,能帮助你写出更高效的代码。

5.1 扩容的触发条件

ArrayList 的扩容是在 “添加元素时” 触发的,核心入口是ensureCapacityInternal(int minCapacity)方法,minCapacity表示 “当前需要的最小容量”(即size + 1,因为要添加 1 个元素)。

扩容的核心逻辑可以概括为:

  1. 计算当前需要的最小容量minCapacity(添加 1 个元素就是size + 1,添加 n 个元素就是size + n);
  2. 比较minCapacity和当前数组容量(elementData.length):
    • 如果minCapacity <= elementData.length:容量足够,无需扩容;
    • 如果minCapacity > elementData.length:容量不足,触发扩容。

5.2 扩容的具体流程(JDK 1.8)

我们通过源码一步步拆解扩容流程,核心方法包括ensureCapacityInternal()ensureExplicitCapacity()grow()

步骤 1:ensureCapacityInternal (int minCapacity)—— 计算最小容量
private void ensureCapacityInternal(int minCapacity) {
    // 如果elementData是默认空数组(无参构造初始化的情况),则最小容量取DEFAULT_CAPACITY(10)和minCapacity的最大值
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 检查是否需要显式扩容
    ensureExplicitCapacity(minCapacity);
}

比如:无参构造初始化后,第一次调用add()minCapacity = size + 1 = 1,此时elementDataDEFAULTCAPACITY_EMPTY_ELEMENTDATA,所以minCapacity会被更新为max(10, 1) = 10

步骤 2:ensureExplicitCapacity (int minCapacity)—— 判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
    modCount++; // 修改计数器(用于迭代器的快速失败机制)

    // 如果最小容量 > 当前数组容量:需要扩容
    if (minCapacity - elementData.length > 0) {
        grow(minCapacity); // 核心扩容方法
    }
}

modCount是 “集合修改次数计数器”,每次 add/remove/clear 操作都会让modCount++,迭代器遍历前会记录expectedModCount = modCount,如果遍历过程中modCount != expectedModCount,就会抛出ConcurrentModificationException(快速失败机制)。

步骤 3:grow (int minCapacity)—— 执行扩容(核心方法)

grow()是 ArrayList 扩容的核心,负责创建新数组、复制元素、替换底层数组,源码:

private void grow(int minCapacity) {
    // 获取当前数组容量(oldCapacity)
    int oldCapacity = elementData.length;
    // 计算新容量:oldCapacity + (oldCapacity >> 1) → 即oldCapacity * 1.5(右移1位相当于除以2)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    // 如果新容量 < 最小容量:则新容量 = 最小容量(避免扩容后仍不够用)
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    
    // 如果新容量 > 最大数组容量(Integer.MAX_VALUE - 8):则新容量取Integer.MAX_VALUE(避免溢出)
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        newCapacity = hugeCapacity(minCapacity);
    }
    
    // 复制原数组元素到新数组(新容量为newCapacity),并替换elementData
    elementData = Arrays.copyOf(elementData, newCapacity);
}

// 处理超大容量的情况
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) { // 溢出(minCapacity为负,说明int超出最大值)
        throw new OutOfMemoryError();
    }
    // 如果最小容量 > MAX_ARRAY_SIZE:返回Integer.MAX_VALUE,否则返回MAX_ARRAY_SIZE
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

// 数组的最大容量(Integer.MAX_VALUE - 8):因为某些JVM会在数组末尾保留一些头部信息
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

扩容流程总结:

  1. 新容量默认是旧容量的 1.5 倍(通过oldCapacity + (oldCapacity >> 1)计算,右移操作比乘法更高效);
  2. 如果 1.5 倍后的新容量仍小于 “最小需要容量”(比如一次性添加大量元素),则新容量直接等于 “最小需要容量”;
  3. 如果新容量超过MAX_ARRAY_SIZEIn
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值