ArrayList 深入解析

在 Java 集合框架中,ArrayList是开发中使用频率最高的集合类之一,它以动态数组的特性、高效的随机访问能力成为 List 接口的主流实现。

一、ArrayList 基础定义

ArrayList 是Java 集合框架java.util包中的动态数组 实现类,它实现了List接口,底层基于Object 类型的数组存储数据。

核心特性:

  1. 动态扩容:无需手动指定容量,集合会根据元素数量自动扩容数组长度;
  2. 有序可重复:元素存储顺序与插入顺序一致,允许存入 null 值和重复元素;
  3. 非线程安全:多线程环境下使用会出现线程安全问题;
  4. 支持随机访问:通过索引可以 O (1) 时间复杂度直接访问元素。

二、ArrayList 的继承与实现关系

ArrayList 的类定义源码如下,清晰展示了其继承体系:

java 复制代码
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

核心继承 / 实现关系解析

  1. 继承AbstractList :抽象父类,实现了 List 接口的通用方法(如add、remove),减少 ArrayList 的重复代码;
  2. 实现List接口:定义了列表的核心规范(增删改查、遍历等);
  3. 实现RandomAccess接口标记型接口 ,表示 ArrayList 支持快速随机访问,遍历优先使用普通 for 循环;
  4. 实现Cloneable接口:支持克隆(浅克隆);
  5. 实现Serializable接口:支持序列化,可在网络传输、本地存储中使用。

关键:RandomAccess是空接口,仅作为标识,JDK 会根据该接口优化集合遍历方式。

三、ArrayList 源码解析

要理解 ArrayList,必须先理解其底层存储结构、核心常量、构造方法、扩容机制,这是原理的核心。

1. 核心成员变量

java 复制代码
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    // 序列化版本号
    private static final long serialVersionUID = 8683452581122863359L;

    /**
     * 默认初始容量:创建ArrayList时不指定容量,默认容量为10
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空数组实例:用于无参构造时的初始存储
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 空数组实例:用于指定容量为0时的存储
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 底层核心存储:ArrayList真正存放数据的Object数组
     * 非私有,方便嵌套类访问
     */
    transient Object[] elementData;

    /**
     * ArrayList中实际存储的元素个数(不是数组长度)
     */
    private int size;
}
关键变量区分
  • elementData:底层数组,长度 = 集合容量
  • size:实际元素数量,永远 ≤ elementData.length;
  • DEFAULT_CAPACITY:默认初始容量 10。

2. 三大构造方法

ArrayList 提供 3 种构造方式,决定了底层数组的初始化逻辑:

(1)无参构造(最常用)
java 复制代码
public ArrayList() {
    // 初始化为空数组,第一次添加元素时才会扩容为默认容量10
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

重点:无参构造不会直接创建长度为 10 的数组,懒加载初始化,节省内存。

(2)指定初始容量的构造
java 复制代码
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        // 创建指定长度的Object数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 容量为0,赋值为空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    }
}

优化建议:预知元素数量时,指定初始容量,避免多次扩容,提升性能。

(3)集合参数构造
java 复制代码
public ArrayList(Collection<? extends E> c) {
    // 将集合转为数组
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // 兼容JDK版本,确保数组类型为Object[]
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 传入空集合,赋值为空数组
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

3. 核心机制:动态扩容(源码)

ArrayList 的动态扩容 是其核心特性,当底层数组存满元素时,会自动创建新数组并复制数据。扩容入口:add方法添加元素前,会检查容量,不足则触发扩容。

步骤 1:add () 方法(添加元素)
java 复制代码
public boolean add(E e) {
    // 检查容量,不足则扩容
    ensureCapacityInternal(size + 1);
    // 元素存入数组,size自增
    elementData[size++] = e;
    return true;
}
步骤 2:ensureCapacityInternal () 计算容量
java 复制代码
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 计算最小需要容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 如果是空数组(无参构造初始化),返回默认容量10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
步骤 3:ensureExplicitCapacity () 判断是否扩容
java 复制代码
private void ensureExplicitCapacity(int minCapacity) {
    // 修改次数+1(用于迭代器快速失败)
    modCount++;

    // 最小需要容量 > 底层数组长度 → 触发扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
步骤 4:grow () 真正扩容(核心)
java 复制代码
private void grow(int minCapacity) {
    // 原数组容量
    int oldCapacity = elementData.length;
    // 新容量 = 原容量 + 原容量/2 → 1.5倍扩容
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    // 特殊情况:新容量 < 最小需要容量(如初始化时)
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    
    // 超过最大数组容量,使用Integer.MAX_VALUE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    
    // 数组复制:创建新数组,将原数组数据复制到新数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}

扩容核心总结

  1. 扩容倍数 :默认1.5 倍扩容oldCapacity >> 1等价于除以 2,位运算效率更高);
  2. 触发时机 :添加元素时,size+1 > 数组容量
  3. 底层实现 :通过Arrays.copyOf创建新数组 + 复制数据(底层调用System.arraycopy本地方法,效率较高);
  4. 缺点:频繁扩容会创建新数组、复制数据,消耗性能,因此建议提前指定容量。

4. 核心方法源码解析

(1)get (int index) 按索引获取元素
java 复制代码
public E get(int index) {
    // 检查索引是否越界
    rangeCheck(index);
    // 直接返回数组对应索引元素,O(1)时间复杂度
    return elementData(index);
}

// 索引越界校验
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

// 数组元素获取(强转泛型)
E elementData(int index) {
    return (E) elementData[index];
}

优势:随机访问效率极高,是 ArrayList 最大的优点。

(2)set (int index, E element) 修改元素
java 复制代码
public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    // 直接替换数组元素
    elementData[index] = element;
    return oldValue;
}
(3)remove (int index) 删除元素
java 复制代码
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回收
    elementData[--size] = null;
    return oldValue;
}

缺点:删除非末尾元素,需要移动数组元素,效率低。

(4)clear () 清空集合
java 复制代码
public void clear() {
    modCount++;
    // 所有元素置空,GC回收
    for (int i = 0; i < size; i++)
        elementData[i] = null;
    size = 0;
}

四、ArrayList 迭代器原理

ArrayList 提供两种迭代器:IteratorListIterator,核心实现是内部类ItrListItr

1. 迭代器核心源码(Itr)

java 复制代码
private class Itr implements Iterator<E> {
    int cursor;       // 下一个要返回的元素索引
    int lastRet = -1; // 上一个返回的元素索引
    int expectedModCount = modCount; // 快速失败标记

    // 判断是否有下一个元素
    public boolean hasNext() {
        return cursor != size;
    }

    // 获取下一个元素
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size) throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    // 快速失败校验:迭代时修改集合,抛出ConcurrentModificationException
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

2. 特性:快速失败(Fail-Fast)

  • modCount:集合修改次数(增 / 删 / 清空都会 + 1);
  • expectedModCount:迭代器初始化时复制modCount
  • 原理:迭代过程中,如果集合被外部线程 / 代码修改modCount会变化,与expectedModCount不相等,立即抛出ConcurrentModificationException,避免遍历数据不一致。

3. 迭代器使用注意事项

  1. 迭代时不能通过集合的 add/remove 方法修改元素,只能用迭代器的 remove 方法;
  2. 多线程环境下,迭代器会触发快速失败,无法保证线程安全。

五、ArrayList 与 LinkedList 对比

两者都是 List 接口实现,但底层结构、性能、适用场景天差地别:

维度 ArrayList LinkedList
底层结构 动态 Object 数组 双向链表
随机访问 支持,O (1) 效率高 不支持,O (n) 效率低
插入 / 删除元素 末尾操作 O (1),中间 O (n) 任意位置 O (1),效率高
内存占用 内存连续,扩容会浪费空间 每个节点存数据 + 前后指针,占用更多内存
线程安全 非线程安全 非线程安全
适用场景 大量查询、读取操作 大量插入、删除操作
扩容机制 1.5 倍动态扩容 无需扩容,链表无限扩展

总结

  • 读多写少 → 选ArrayList(开发主流选择);
  • 写多读少 → 选LinkedList
  • 两者均非线程安全,多线程用CopyOnWriteArrayList

六、ArrayList 面试高频考点

1. ArrayList 的底层原理?

基于Object 数组 实现,支持1.5 倍动态扩容,非线程安全的动态数组,支持快速随机访问。

2. 无参构造的 ArrayList 初始容量是多少?

无参构造初始化时是空数组 ,第一次添加元素时才会扩容为默认容量 10(懒加载)。

3. ArrayList 扩容机制?

添加元素时容量不足,会创建1.5 倍原容量 的新数组,通过System.arraycopy复制原数组数据。

4. 为什么 ArrayList 查询快,增删慢?

  • 查询:数组支持索引随机访问,O (1) 时间复杂度;
  • 增删:中间位置增删需要移动后续所有元素,O (n) 时间复杂度。

5. ArrayList 迭代时为什么不能修改集合?

因为快速失败机制 ,迭代器会校验modCount,集合被修改后抛出异常,防止遍历数据错乱。

6. ArrayList 和数组的区别?

  • 数组:长度固定,只能存同类型数据,无内置方法;
  • ArrayList:动态扩容,支持泛型,提供丰富的增删改查方法。

7. ArrayList 线程安全吗?如何实现线程安全?

  • 不安全;
  • 解决方案:
    1. 使用Vector(低效,不推荐);
    2. Collections.synchronizedList()包装;
    3. 推荐:JUC 包下的CopyOnWriteArrayList

8. ArrayList 为什么用 transient 修饰 elementData?

elementData是底层数组,数组长度(容量)大于实际元素数量(size),序列化时直接序列化数组会浪费空间。ArrayList 重写writeObject方法,只序列化实际元素,节省空间。

七、总结

ArrayList 作为 Java 最常用的集合,核心是动态数组 + 1.5 倍扩容 + 随机访问,它的优势在于查询读取,劣势在于中间位置增删。

开发中遵循两个优化原则:

  1. 预知元素数量时,指定初始容量,避免频繁扩容;
  2. 读多写少场景优先使用 ArrayList,写多读少使用 LinkedList。
相关推荐
算.子1 小时前
【Spring AI 实战】五、RAG 核心原理:为什么需要检索增强生成?
java·人工智能·spring
XS0301061 小时前
Java基础笔记(一)
java·笔记·python
程序员老邢1 小时前
【产品底稿 05】商助慧 V1.1 里程碑:RAG 文章仿写模块全链路实现
java·spring boot·程序人生·ai·milvus
消失的旧时光-19432 小时前
Spring Boot 实战(三):Service 分层 + 统一返回 + 异常处理(工程级写法)
java·spring boot·接口·解耦
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【20】MessagesAgentHook 、MessagesModelHook 相关实现类
java·人工智能·spring
霸道流氓气质2 小时前
SpringBoot中集成LangChain4j实现集成阿里百炼平台进行AI对话记忆功能和对话隔离功能
java·人工智能·spring boot·langchain4j
XS0301062 小时前
Java 基础笔记(二)
java·笔记·python
papaofdoudou2 小时前
AMD-V 嵌套分页白皮书翻译
java·linux·服务器