ArrayList 源码深度分析(基于 JDK 8)

ArrayList 源码深度分析(基于 JDK 8)

ArrayList 是 Java 集合框架中最常用的动态数组实现 ,继承自 AbstractList,实现了 ListRandomAccessCloneableSerializable 接口,核心特点是支持随机访问、动态扩容、非线程安全,适用于「查询多、增删少」的场景。

一、类结构与核心接口

先看 ArrayList 的类定义,理解其继承体系和核心能力:

复制代码
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    // ... 源码内容
}

各接口的作用:

  • List<E>:核心列表接口,定义了增删改查、遍历等基础操作(如 addremoveget)。

  • RandomAccess标记接口 (无抽象方法),表明该集合支持「随机访问」(通过索引直接获取元素,时间复杂度 O (1)),用于优化遍历效率(如 Collections.binarySearch 会根据是否实现该接口选择不同遍历方式)。

  • Cloneable:支持调用 clone() 方法实现「浅克隆」。

  • Serializable:支持序列化(通过自定义 writeObject/readObject 优化序列化效率)。

二、核心成员变量

ArrayList 的底层是数组,核心变量决定了其存储特性和扩容逻辑:

复制代码
// 1. 默认初始容量(无参构造时,第一次 add 才初始化)
private static final int DEFAULT_CAPACITY = 10;
​
// 2. 空数组(用户指定容量为 0 或传入空集合时使用)
private static final Object[] EMPTY_ELEMENTDATA = {};
​
// 3. 默认空数组(无参构造专用,与 EMPTY_ELEMENTDATA 区分,用于第一次扩容判断)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
​
// 4. 存储元素的核心数组(transient 修饰:不参与默认序列化,自定义序列化优化)
transient Object[] elementData;
​
// 5. 集合中实际元素个数(≠ elementData 数组长度)
private int size;
​
// 6. 最大容量(避免扩容时数组长度溢出,Integer.MAX_VALUE - 8 是因为虚拟机对数组头有额外存储)
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

关键区分:EMPTY_ELEMENTDATA vs DEFAULTCAPACITY_EMPTY_ELEMENTDATA

  • 无参构造 new ArrayList<>()elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,第一次 add 时直接扩容到 DEFAULT_CAPACITY(10)

  • 指定容量为 0 new ArrayList<>(0) 或传入空集合 new ArrayList<>(Collections.emptyList())elementData = EMPTY_ELEMENTDATA,第一次 add 时扩容到 1(而非 10)。

三、构造方法

ArrayList 提供 3 个核心构造方法,对应不同初始化场景:

1. 无参构造(最常用)

复制代码
public ArrayList() {
    // 初始化时不分配容量,第一次 add 才扩容到 10
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

懒加载设计:避免无参创建后不使用导致的内存浪费。

2. 指定初始容量构造

复制代码
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        // 容量 >0:直接创建对应长度的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 容量 =0:复用空数组 EMPTY_ELEMENTDATA
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    }
}

优化场景:已知集合大致大小(如 1000 个元素),直接指定容量可避免多次扩容,提升性能。

3. 传入集合构造

复制代码
public ArrayList(Collection<? extends E> c) {
    // 把集合转为数组
    elementData = c.toArray();
    // 更新实际元素个数
    if ((size = elementData.length) != 0) {
        // 若数组类型不是 Object[],则拷贝为 Object[](避免类型转换异常)
        if (elementData.getClass() != Object[].class) {
            elementData = Arrays.copyOf(elementData, size, Object[].class);
        }
    } else {
        // 集合为空:复用 EMPTY_ELEMENTDATA
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

注意c.toArray() 可能返回非 Object[] 类型(如 String[]),需通过 Arrays.copyOf 转为 Object[],保证 ArrayList 底层数组类型统一。

四、核心方法解析

(一)添加元素:add(E e)add(int index, E element)

添加元素是 ArrayList 的核心操作,涉及「容量检查」和「扩容」逻辑。

1. 尾部添加:add(E e)(时间复杂度 O (1),扩容时 O (n))
复制代码
public boolean add(E e) {
    // 1. 确保容量(核心:判断是否需要扩容)
    ensureCapacityInternal(size + 1); // size+1 是本次添加所需的最小容量
    // 2. 元素存入数组,size 自增
    elementData[size++] = e;
    return true;
}
2. 指定索引添加:add(int index, E element)(时间复杂度 O (n))
复制代码
public void add(int index, E element) {
    // 1. 检查索引合法性(index 必须在 [0, size] 之间)
    rangeCheckForAdd(index);
    // 2. 确保容量
    ensureCapacityInternal(size + 1);
    // 3. 移动数组元素:从 index 开始的元素向后移 1 位(核心开销来源)
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    // 4. 插入元素,size 自增
    elementData[index] = element;
    size++;
}

关键System.arraycopy 是 native 方法,效率较高,但移动元素的本质是「拷贝」,元素越多(size - index 越大),开销越大,这也是 ArrayList 插入慢的原因。

(二)扩容核心逻辑:ensureCapacityInternalensureExplicitCapacitygrow

扩容是 ArrayList 动态数组的核心,触发条件:size + 1 > elementData.length(添加元素后超出当前数组容量)。

1. 计算最小容量:calculateCapacity
复制代码
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 若为无参构造的初始状态(DEFAULTCAPACITY_EMPTY_ELEMENTDATA),最小容量取 10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 其他情况:最小容量 = 所需容量(size+1)
    return minCapacity;
}
2. 检查是否需要扩容:ensureExplicitCapacity
复制代码
private void ensureExplicitCapacity(int minCapacity) {
    // modCount:记录集合修改次数(用于 fail-fast 机制)
    modCount++;
​
    // 最小容量 > 数组长度 → 触发扩容
    if (minCapacity - elementData.length > 0) {
        grow(minCapacity);
    }
}
3. 实际扩容:grow(核心方法)
复制代码
private void grow(int minCapacity) {
    // 1. 获取当前数组长度
    int oldCapacity = elementData.length;
    // 2. 扩容公式:新容量 = 旧容量 + 旧容量/2(即 1.5 倍扩容)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
​
    // 3. 处理特殊情况:
    // - 若旧容量为 0(如 new ArrayList<>(0)),newCapacity 会是 0,需修正为 minCapacity(1)
    // - 若扩容后溢出(newCapacity < 0),直接用最大容量 MAX_ARRAY_SIZE
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        newCapacity = hugeCapacity(minCapacity);
    }
​
    // 4. 拷贝原数组元素到新数组(扩容的本质:创建新数组 + 拷贝元素)
    elementData = Arrays.copyOf(elementData, newCapacity);
}
​
// 处理超大容量需求
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) { // 溢出(minCapacity 为负数)
        throw new OutOfMemoryError();
    }
    // 最小容量超过 MAX_ARRAY_SIZE → 用 Integer.MAX_VALUE,否则用 MAX_ARRAY_SIZE
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

扩容关键结论

  • 默认扩容为原容量的 1.5 倍(位运算 oldCapacity >> 1 比除法 oldCapacity / 2 效率高)。

  • 扩容本质是「创建新数组 + 拷贝元素」,时间复杂度 O (n),频繁扩容会影响性能(建议已知大小时光指定初始容量)。

  • 特殊场景:初始容量为 0 时,第一次 add 扩容到 1,第二次 add 扩容到 1.5(取整为 1)→ 仍不够,扩容到 2,第三次到 3,第四次到 4,以此类推。

(三)删除元素:remove(int index)remove(Object o)

删除元素同样需要移动数组,时间复杂度 O (n)。

1. 按索引删除:remove(int index)
复制代码
public E remove(int index) {
    // 1. 检查索引合法性(index < size)
    rangeCheck(index);
    modCount++;
    // 2. 保存要删除的元素(返回用)
    E oldValue = elementData(index);
​
    // 3. 计算需要移动的元素个数
    int numMoved = size - index - 1;
    if (numMoved > 0) {
        // 从 index+1 开始的元素向前移 1 位(覆盖被删除元素)
        System.arraycopy(elementData, index + 1, elementData, index, numMoved);
    }
    // 4. 释放最后一个元素的引用(避免内存泄漏)
    elementData[--size] = null;
​
    return oldValue;
}
2. 按元素删除:remove(Object o)
复制代码
public boolean remove(Object o) {
    if (o == null) {
        // 遍历查找 null 元素(ArrayList 允许 null)
        for (int index = 0; index < size; index++) {
            if (elementData[index] == null) {
                fastRemove(index); // 快速删除(无索引检查,无返回值)
                return true;
            }
        }
    } else {
        // 遍历查找 equals 匹配的元素
        for (int index = 0; index < size; index++) {
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
        }
    }
    return false;
}
​
// 快速删除:跳过索引检查和返回值,优化性能
private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0) {
        System.arraycopy(elementData, index + 1, elementData, index, numMoved);
    }
    elementData[--size] = null;
}

注意remove(Object o) 只删除「第一个匹配的元素」,且 null 元素通过 == 判断,非 null 元素通过 equals 判断。

(四)查询与修改:get(int index)set(int index, E element)

因底层是数组,支持随机访问,查询和修改的时间复杂度均为 O (1):

复制代码
// 查询元素
public E get(int index) {
    rangeCheck(index); // 检查索引 < size
    return elementData(index); // 直接返回数组对应位置元素((E) elementData[index])
}
​
// 修改元素
public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element; // 直接覆盖数组元素
    return oldValue;
}

五、序列化优化

ArrayList 的 elementDatatransient 修饰,说明不参与 JVM 默认序列化。为何要这么设计?

  • 因为 elementData 数组的长度可能大于实际元素个数(扩容后未填满),默认序列化会把空元素也序列化,浪费空间。

ArrayList 通过自定义序列化 / 反序列化方法优化:

复制代码
// 序列化:只写入实际存在的元素(size 个)
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
    // 1. 写入 modCount(用于反序列化时校验)
    int expectedModCount = modCount;
    s.defaultWriteObject();
​
    // 2. 写入实际元素个数
    s.writeInt(size);
​
    // 3. 逐个写入元素(只写前 size 个,跳过空元素)
    for (int i = 0; i < size; i++) {
        s.writeObject(elementData[i]);
    }
​
    // 4. 校验序列化过程中集合是否被修改(fail-fast)
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}
​
// 反序列化:恢复元素到数组
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;
​
    // 1. 读取非 transient 变量(如 size、modCount)
    s.defaultReadObject();
​
    // 2. 读取元素个数
    int size = s.readInt();
​
    if (size > 0) {
        // 3. 计算容量(若为无参构造初始化,容量取 10;否则取 size)
        int capacity = calculateCapacity(elementData, size);
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
        // 4. 初始化数组并读取元素
        elementData = new Object[capacity];
        for (int i = 0; i < size; i++) {
            elementData[i] = s.readObject();
        }
    }
}

六、迭代器与 Fail-Fast 机制

ArrayList 的迭代器 Itr 是内部类,实现了 Iterator 接口,核心是「快速失败(Fail-Fast)」机制:遍历过程中若集合被结构性修改(add/remove 等),直接抛出 ConcurrentModificationException,避免数据不一致。

1. 迭代器核心变量

复制代码
private class Itr implements Iterator<E> {
    int cursor;       // 下一个要返回的元素索引
    int lastRet = -1; // 上一个返回的元素索引(-1 表示未返回或已删除)
    // 期望的修改次数(初始等于集合的 modCount)
    int expectedModCount = modCount;
​
    // ... 方法实现
}

2. Fail-Fast 触发逻辑

迭代器的所有操作(next()remove())都会先调用 checkForComodification()

复制代码
final void checkForComodification() {
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}
  • 集合结构性修改时(add/remove),modCount 会自增。

  • 若迭代器的 expectedModCountmodCount 不一致,说明集合被外部修改,触发异常。

3. 迭代器的 remove() 方法(安全删除)

普通 remove(int index) 会导致 modCount 自增,触发 Fail-Fast,但迭代器的 remove() 不会:

复制代码
public void remove() {
    if (lastRet < 0) {
        throw new IllegalStateException();
    }
    checkForComodification();
​
    try {
        // 调用集合的 remove 方法
        ArrayList.this.remove(lastRet);
        // 更新 cursor(避免跳过元素)
        cursor = lastRet;
        lastRet = -1;
        // 同步 expectedModCount 与 modCount
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

关键 :删除后会更新 expectedModCount = modCount,保证后续操作不会触发异常。

七、其他重要方法

1. trimToSize():缩容优化

将数组长度缩为实际元素个数,节省内存(适用于集合不再添加元素的场景):

复制代码
public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
    }
}

2. ensureCapacity(int minCapacity):手动扩容

提前指定最小容量,避免多次自动扩容(如批量添加大量元素时):

复制代码
public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}

3. subList(int fromIndex, int toIndex):子列表视图

返回原集合的「子列表视图」,而非新集合:修改子列表会影响原集合,反之亦然,且子列表的迭代器也遵循 Fail-Fast 机制。

八、ArrayList 核心特性总结

特性 说明
底层实现 动态数组(Object [])
访问效率 随机访问 O (1)(直接索引)
增删效率 尾部添加 O (1)(无扩容),插入 / 删除 O (n)(需移动元素)
容量机制 初始容量 10(无参构造),默认扩容 1.5 倍
线程安全 非线程安全(多线程操作需手动同步)
元素限制 允许 null 元素,允许重复元素
遍历机制 支持迭代器、增强 for、普通 for(推荐普通 for 遍历,效率最高)
序列化 自定义序列化,只序列化实际元素
故障机制 Fail-Fast(遍历中修改集合抛异常)

九、适用场景与注意事项

1. 适用场景

  • 频繁查询(get)、尾部添加(add(E e))的场景。

  • 已知集合大小,可指定初始容量优化性能。

2. 注意事项

  • 非线程安全:多线程环境下需使用 Collections.synchronizedList(new ArrayList<>())CopyOnWriteArrayList

  • 避免频繁插入 / 删除:若需频繁在中间位置增删,建议使用 LinkedList(双向链表,增删 O (1),查询 O (n))。

  • 扩容开销:批量添加元素时,提前调用 ensureCapacity 减少扩容次数。

  • Fail-Fast 不是线程安全保证:仅用于开发调试,不能依赖其处理并发修改(并发场景需用线程安全集合)。

总结

ArrayList 的设计核心是「动态数组 + 高效随机访问」,通过「1.5 倍扩容」「懒加载初始化」「自定义序列化」等优化兼顾性能和内存效率。其源码的关键亮点包括:

  1. 区分 DEFAULTCAPACITY_EMPTY_ELEMENTDATAEMPTY_ELEMENTDATA,优化无参构造和零容量构造的扩容逻辑。

  2. transient 修饰数组,自定义序列化减少空间浪费。

  3. Fail-Fast 机制通过 modCount 保证遍历安全性。

  4. 核心操作(扩容、移动元素)依赖 Arrays.copyOfSystem.arraycopy(native 方法,效率高)。

相关推荐
4***W4291 小时前
SpringCloud实战十三:Gateway之 Spring Cloud Gateway 动态路由
java·spring cloud·gateway
safestar20121 小时前
Spring Boot的魔法与陷阱:从自动配置原理到生产环境避坑实战
java·spring boot·后端
高洁011 小时前
具身智能-视觉语言导航(VLN)(3
深度学习·神经网络·算法·aigc·transformer
v***55341 小时前
Spring Boot环境配置
java·spring boot·后端
达不溜先生 ୧⍢⃝୨1 小时前
循环赛日程表问题
c语言·算法·递归·分治·循环赛日程表·动态二维数组
J***51681 小时前
Spring Cloud GateWay搭建
java
IT·小灰灰1 小时前
深度解析重排序AI模型:基于硅基流动API调用多语言重排序AI实战指南
java·大数据·javascript·人工智能·python·数据挖掘·php
一辉ComeOn1 小时前
【大数据高并发核心场景实战】 数据持久化层 - 分表分库
java·大数据·分布式·mysql·系统架构
y***03171 小时前
Go基础之环境搭建
开发语言·后端·golang