ArrayList扩容机制深度解析(附时序图详细讲解)

一、ArrayList底层数据结构

📦 核心结构

java 复制代码
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    // 底层存储数组(transient表示不参与序列化)
    transient Object[] elementData;
    
    // 实际元素个数
    private int size;
    
    // 默认初始容量
    private static final int DEFAULT_CAPACITY = 10;
    
    // 空数组常量
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    private static final Object[] EMPTY_ELEMENTDATA = {};
}

关键点

  • elementData:真正存储元素的数组
  • size:当前数组中元素的实际数量
  • DEFAULT_CAPACITY:默认初始容量为10

完整扩容流程时序图

📊 场景:添加第11个元素(触发扩容)

二、分步详解(附源码)

🔍 步骤1:客户端调用add()方法

java 复制代码
// 客户端代码
ArrayList<String> list = new ArrayList<>();
list.add("Hello");  // 触发扩容流程

时序说明

  • 客户端调用list.add("Hello")
  • 进入ArrayList的add(E e)方法

🔍 步骤2:调用ensureCapacityInternal()

java 复制代码
// ArrayList源码
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 确保容量足够
    elementData[size++] = e;           // 添加元素
    return true;
}

时序说明

  • add()方法首先调用ensureCapacityInternal(size + 1)
  • 参数minCapacity = size + 1(所需最小容量)

🔍 步骤3:计算所需容量(calculateCapacity)

java 复制代码
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 如果是空数组,返回默认容量10和minCapacity的较大值
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

时序说明

  • 判断当前数组是否为空数组
  • 首次添加 :返回max(10, minCapacity)10
  • 后续添加 :直接返回minCapacity

🔍 步骤4:确保显式容量(ensureExplicitCapacity)

java 复制代码
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;  // 修改次数+1(用于fail-fast机制)
    
    // 如果所需容量超过当前数组长度,触发扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

时序说明

  • modCount++:记录修改次数(用于迭代器的fail-fast检查)
  • 判断是否需要扩容:minCapacity > elementData.length

🔍 步骤5:扩容核心逻辑(grow方法)

java 复制代码
private void grow(int minCapacity) {
    // 1. 获取旧容量
    int oldCapacity = elementData.length;
    
    // 2. 计算新容量 = 旧容量 + 旧容量/2(即1.5倍)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    // 3. 如果1.5倍仍不够,直接使用所需容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    
    // 4. 处理最大容量限制
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    
    // 5. 复制数组到新数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}

时序说明

  1. 获取当前数组长度oldCapacity
  2. 计算新容量:oldCapacity + (oldCapacity >> 1)(位运算相当于除以2)
  3. 检查新容量是否满足需求,不足则用minCapacity
  4. 检查是否超过最大限制Integer.MAX_VALUE - 8
  5. 调用Arrays.copyOf()创建新数组并复制元素

🔍 步骤6:数组复制(Arrays.copyOf)

java 复制代码
// Arrays.copyOf底层调用System.arraycopy(native方法)
public static <T> T[] copyOf(T[] original, int newLength) {
    return (T[]) copyOf(original, newLength, original.getClass());
}

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    return copy;
}

时序说明

  • 创建新数组(长度为newCapacity
  • 调用System.arraycopy()(native方法,高效复制)
  • 返回新数组,替换原elementData

🔍 步骤7:添加元素并返回

java 复制代码
// 回到add()方法
elementData[size++] = e;  // 将元素放入数组
return true;              // 返回成功

时序说明

  • 扩容完成后,将元素放入elementData[size]位置
  • size++:元素计数+1
  • 返回true,流程结束

三、不同场景的扩容时序图

📊 场景1:首次添加元素(从空数组到容量10)

关键点

  • 初始数组为空{}
  • calculateCapacity返回10(默认容量)
  • grow中1.5倍计算为0,不足则直接用10

📊 场景2:第11个元素添加(1.5倍扩容)

复制代码

关键点

  • 旧容量10 → 新容量15(10 + 5)
  • System.arraycopy复制10个元素到新数组
  • 剩余5个位置为null

📊 场景3:批量添加触发扩容(addAll)

复制代码

关键点

  • addAll一次性添加多个元素
  • 1.5倍扩容后仍不足,直接使用所需容量20

总结:扩容流程关键点

步骤 方法 关键操作 时间复杂度
1 add() 调用扩容检查 O(1)
2 ensureCapacityInternal() 计算所需容量 O(1)
3 calculateCapacity() 首次返回10,后续返回minCapacity O(1)
4 ensureExplicitCapacity() 判断是否需要扩容 O(1)
5 grow() 计算新容量(1.5倍) O(1)
6 Arrays.copyOf() 数组复制 O(n)
7 add() 添加元素 O(1)

🎯 核心要点

  1. 扩容触发条件size + 1 > elementData.length
  2. 扩容倍数 :1.5倍(old + old/2
  3. 首次扩容:空数组 → 容量10
  4. 扩容成本:数组复制是**O(n)**操作
  5. 性能优化:预估容量可避免多次扩容
相关推荐
RisunJan1 小时前
Linux命令-lvextend(扩展逻辑卷空间)
linux·运维·服务器
艾莉丝努力练剑2 小时前
【Linux:文件】进程间通信
linux·运维·服务器·c语言·网络·c++·人工智能
逆境不可逃2 小时前
【从零入门23种设计模式03】创建型之建造者模式(简易版与导演版)
java·后端·学习·设计模式·职场和发展·建造者模式
The森2 小时前
Linux IO模型纵深解析:文章索引
linux·运维·服务器
心本无晴.2 小时前
LangGraph 进阶指南:从状态机到生产级多智能体架构的全面进化
linux·windows·microsoft
专注VB编程开发20年2 小时前
c# CSRedisCore vb.net清空所有键值
数据库·oracle
程序边界2 小时前
数据库MySQL兼容版权限隔离深度体验:从兼容到安全增强的实战之路
数据库·mysql·安全
Victoria.a2 小时前
Mysql基础
数据库·mysql