作为 JDK 集合框架中最常用的 List 实现类,ArrayList 几乎是每个 Java 开发者日常开发的 “标配”。它看似简单易用,但其底层的动态数组设计、扩容机制、迭代器实现等细节,藏着很多提升代码效率的关键逻辑。本文基于 JDK8 版本,从源码角度拆解 ArrayList 的核心实现,帮你彻底搞懂它的 “底层逻辑”。

一、ArrayList 核心定位

先明确 ArrayList 的核心特征,这是理解源码的基础:

  • 底层结构:基于动态数组实现(数组容量可自动扩容);

  • 核心特性:有序、可重复、支持索引快速访问,查询效率高(O (1))、增删效率低(尤其是中间位置,O (n));

  • 线程安全:非线程安全(方法未加锁),多线程环境下需手动同步(如 Collections.synchronizedList);

  • JDK8 优化:无重大结构变更,但优化了扩容时的数组复制效率、新增 removeIf 等方法。

二、核心成员变量(源码关键)

先看 ArrayList 类中定义的核心变量,这些是理解后续逻辑的关键:

// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组(用于空实例初始化)
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认空数组(用于无参构造,区别于EMPTY_ELEMENTDATA,标记首次添加元素时扩容至10)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储元素的核心数组(transient表示序列化时忽略,自定义序列化逻辑)
transient Object[] elementData;
// 集合中实际元素个数(区别于数组容量)
private int size;
// 数组最大容量(避免OOM,Integer.MAX_VALUE - 8)
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

关键区分EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA 是 JDK8 对空数组的精细化设计,目的是区分 “用户指定容量为 0” 和 “无参构造” 两种场景,保证首次添加元素时的扩容逻辑统一。

三、构造方法解析

ArrayList 提供了 3 种构造方法,核心是初始化 elementData 数组:

1. 无参构造(最常用)

public ArrayList() {
    // 初始化为默认空数组,首次添加元素时才扩容至10
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

注意:JDK8 中无参构造不再直接初始化容量为 10 的数组,而是延迟到首次添加元素时,减少空实例的内存占用。

2. 指定初始容量

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        // 初始化指定容量的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 容量为0时使用EMPTY_ELEMENTDATA
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    }
}

最佳实践:如果能预估元素个数,优先使用此构造方法指定容量,避免后续频繁扩容。

3. 基于集合构造

public ArrayList(Collection<? extends E> c) {
    // 集合转数组
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // 修复c.toArray()可能返回非Object[]的问题(JDK8兼容处理)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 空集合则赋值为空数组
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

四、核心方法源码解析

1. 添加元素:add (E e)

添加元素是 ArrayList 最核心的操作,涉及 “容量检查→扩容→赋值” 三步:

public boolean add(E e) {
    // 1. 确保数组容量足够(size+1)
    ensureCapacityInternal(size + 1);
    // 2. 元素赋值到数组末尾,size自增
    elementData[size++] = e;
    return true;
}

// 第一步:计算最小容量
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 区分“默认空数组”和“用户指定空数组”的容量计算
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 如果是无参构造的默认空数组,最小容量取10和minCapacity的最大值
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

// 第二步:判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
    modCount++; // 记录修改次数(快速失败机制)
    // 最小容量 > 数组当前长度 → 触发扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

// 第三步:核心扩容逻辑
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 扩容公式:新容量 = 旧容量 + 旧容量/2(即1.5倍)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 处理扩容后仍不足的情况(如初始容量0,首次扩容需到10)
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 处理超大容量(超过MAX_ARRAY_SIZE)
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 数组复制(核心:Arrays.copyOf底层是System.arraycopy,native方法,效率高)
    elementData = Arrays.copyOf(elementData, newCapacity);
}

// 处理超大容量的边界情况
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // 溢出
        throw new OutOfMemoryError();
    // 超过MAX_ARRAY_SIZE则取Integer.MAX_VALUE,否则取MAX_ARRAY_SIZE
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

核心总结

  • 扩容倍数:JDK8 中固定为 1.5 倍(位运算 oldCapacity >> 1 等价于除以 2,效率更高);

  • 扩容本质:创建新数组 + 复制原数组元素,是耗时操作,所以预估容量很重要;

  • modCount:记录集合修改次数,迭代器遍历会校验此值,不一致则抛 ConcurrentModificationException

2. 指定位置添加:add (int index, E element)

public void add(int index, E element) {
    // 校验索引合法性(index < 0 或 > size 抛异常)
    rangeCheckForAdd(index);
    // 容量检查
    ensureCapacityInternal(size + 1);
    // 核心:数组元素后移(System.arraycopy是native方法,效率优于手动循环)
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    // 赋值到指定位置
    elementData[index] = element;
    size++;
}

性能痛点:此方法需要移动 size - index 个元素,索引越靠前,移动元素越多,效率越低(这也是 ArrayList 增删慢的核心原因)。

3. 获取元素:get (int index)

public E get(int index) {
    // 校验索引(index >= size 抛异常)
    rangeCheck(index);
    // 直接返回数组指定位置元素(O(1)效率,ArrayList的核心优势)
    return elementData(index);
}

// 内部方法,强转类型
@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}

核心优势:数组的随机访问特性,决定了 get 方法的时间复杂度为 O (1),这是 ArrayList 对比 LinkedList 的最大优势。

4. 删除元素:remove (int index)

public E remove(int index) {
    // 索引校验
    rangeCheck(index);
    modCount++;
    // 获取待删除元素
    E oldValue = elementData(index);
    // 计算需要移动的元素个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 元素前移,覆盖待删除位置
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    // 清空最后一个位置的引用(帮助GC),size自减
    elementData[--size] = null;
    return oldValue;
}

注意:删除后会将最后一个元素的引用置为 null,避免内存泄漏,但数组容量不会自动缩小(如需缩容,可调用 trimToSize() 方法)。

5. JDK8 新增:removeIf (Predicate<? super E> filter)

@Override
public boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    int removeCount = 0;
    // 标记需要删除的元素
    final BitSet removeSet = new BitSet(size);
    final int expectedModCount = modCount;
    final int size = this.size;
    // 遍历标记需要删除的元素
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        @SuppressWarnings("unchecked")
        final E element = (E) elementData[i];
        if (filter.test(element)) {
            removeSet.set(i);
            removeCount++;
        }
    }
    // 校验并发修改
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }

    // 计算需要保留的元素,前移覆盖
    final boolean anyToRemove = removeCount > 0;
    if (anyToRemove) {
        final int newSize = size - removeCount;
        for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
            i = removeSet.nextClearBit(i);
            elementData[j] = elementData[i];
        }
        // 清空末尾引用
        for (int k=newSize; k < size; k++) {
            elementData[k] = null;
        }
        this.size = newSize;
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }
    return anyToRemove;
}

核心价值:JDK8 新增的批量删除方法,底层通过 BitSet 标记待删除元素,一次性完成数组前移,避免了遍历删除时的并发修改异常,效率远高于手动循环删除。

五、迭代器(Iterator)解析

ArrayList 的迭代器由内部类 Itr 实现,核心是 “快速失败(fail-fast)” 机制:

private class Itr implements Iterator<E> {
    int cursor;       // 下一个要访问的索引
    int lastRet = -1; // 上一个访问的索引
    int expectedModCount = modCount; // 迭代器创建时的modCount

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        // 校验modCount,不一致则抛异常
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            // 调用ArrayList的remove方法
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            // 更新expectedModCount,避免后续校验失败
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

关键规则

  • 迭代器创建时记录 modCount,遍历过程中如果集合被修改(如 add/remove),modCount 变化,调用 next() 会立即抛 ConcurrentModificationException

  • 只有通过迭代器的 remove() 方法删除元素,才会更新 expectedModCount,避免异常(这也是遍历删除的正确方式)。

六、JDK8 ArrayList 核心优化与注意事项

1. 核心优化(对比 JDK7)

  • 无参构造延迟初始化:空实例不再占用 10 个元素的数组空间,节省内存;

  • 扩容时数组复制效率优化:底层 System.arraycopy 的 native 实现进一步优化;

  • 新增函数式方法:removeIfforEach 等,适配 Lambda 表达式。

2. 注意事项

  • 线程安全:非线程安全,多线程环境下建议使用 CopyOnWriteArrayList 或手动加锁;

  • 扩容耗时:频繁扩容会降低性能,建议初始化时指定容量;

  • 内存泄漏:删除元素后虽清空引用,但数组容量不变,如需释放内存可调用 trimToSize()

  • 快速失败:迭代器遍历过程中,禁止直接修改集合(add/remove),需使用迭代器的 remove() 或 JDK8 的 removeIf

七、总结

JDK8 中的 ArrayList 核心是 “动态数组 + 1.5 倍扩容 + 快速失败”,其设计处处体现 “查询优先” 的思想:

  1. 基于数组实现,保证 get 方法 O (1) 效率;

  2. 扩容机制平衡了 “内存占用” 和 “扩容频率”,1.5 倍扩容是时间 / 空间的最优解;

  3. JDK8 新增的函数式方法(如 removeIf)解决了传统遍历删除的痛点;

  4. 快速失败机制避免了并发修改导致的脏数据问题。

文章作者: Z
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 微博客
基础 Java 源码 集合 java
喜欢就支持一下吧