ArrayList 是 Java 集合框架中最常用的动态数组实现,位于 java.util 包。它实现了 List 接口,允许存储重复元素和 null 值。与普通数组相比,ArrayList 的大小可以动态调整,无需在创建时指定固定长度。
由于 ArrayList 内部采用数组进行实现,我们可以从数组的两 大核心特性去了解 ArrayList:
数组在内存中是连续存储的空间,每个元素的内存地址可通过 “数组起始地址 + 元素索引 × 元素占用字节数” 直接计算得出(类似 “知道小区门牌号和单元号,能直接找到住户”)。
这种定位方式无需像链表那样逐个节点遍历查找,因此无论数组长度多少,通过索引查询元素的时间复杂度都是 O(1)(即 “常数级速度”),遍历(按索引顺序访问所有元素)时也能高效利用 CPU 缓存(缓存偏好连续内存片段),进一步提升速度。

数组的长度在初始化后通常固定(或需显式扩容),增删元素会打破原有连续结构,需额外操作:
删除数组中间某元素后,其后面所有元素需 “向前移动一位” 以填补空位(避免内存间隙),若数组长度大,移动的元素数量多。若删除后需缩小数组长度,还需拷贝剩余元素到新的小容量数组中。

若数组已满,需先 “扩容”(如 ArrayList 默认扩为原容量 1.5 倍)—— 创建新的大容量数组,将原数组元素全部拷贝过去。即便数组未满,若在中间插入元素,其后面所有元素需 “向后移动一位” 以腾出位置。这些 “移动 / 拷贝” 操作的时间复杂度随数组长度增加而增加,为 O(n)(n 为数组长度),因此增删效率远低于查询。

ArrayList 提供了三种构造方法,用于初始化不同状态的列表,下面分别介绍:
无参构造函数,创建一个初始为空的列表。底层使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 空数组,首次添加元素时会自动扩容至 DEFAULT_CAPACITY。定义如下:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}创建一个具有指定初始容量的空列表,定义如下:
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);
}
}创建一个包含指定集合所有元素的列表,元素顺序与集合的迭代器顺序一致。定义如下:
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;
}
}
下面通过代码示例展示 ArrayList 的常用操作:
package com.hxstrive.java_collection.arrayList;
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo {
public static void main(String[] args) {
// 1. 创建 ArrayList
List<String> fruits = new ArrayList<>();
// 2. 添加元素
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
fruits.add(1, "Blueberry"); // 在指定索引插入
System.out.println("添加后: " + fruits);
// 输出: [Apple, Blueberry, Banana, Cherry]
// 3. 获取元素
String first = fruits.get(0);
System.out.println("第一个元素: " + first); // 输出: Apple
// 4. 修改元素
fruits.set(2, "Grape");
System.out.println("修改后: " + fruits);
// 输出: [Apple, Blueberry, Grape, Cherry]
// 5. 删除元素
fruits.remove(3); // 按索引删除
fruits.remove("Blueberry"); // 按元素删除
System.out.println("删除后: " + fruits); // 输出: [Apple, Grape]
// 6. 遍历元素
System.out.println("遍历元素:");
for (String fruit : fruits) {
System.out.println(fruit);
}
// 7. 其他常用方法
System.out.println("是否包含 Apple: " + fruits.contains("Apple")); // true
System.out.println("元素数量: " + fruits.size()); // 2
System.out.println("是否为空: " + fruits.isEmpty()); // false
// 8. 清空列表
fruits.clear();
System.out.println("清空后: " + fruits); // 输出: []
}
}
下面将通过分析 ArrayList 的源码了解定义的成员变量,以及它们的用法:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
@java.io.Serial
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认初始容量,当首次添加元素时,空列表会扩容到此容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 共享的空数组实例,用于构造函数指定初始容量为0时使用
* 与DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别在于扩容策略不同
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 共享的空数组实例,用于默认构造函数创建的空列表
* 与EMPTY_ELEMENTDATA区分开来,以便在首次添加元素时知道需要扩容到DEFAULT_CAPACITY
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储ArrayList元素的底层数组缓冲区
* ArrayList的容量就是这个数组的长度
* 对于elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空列表,
* 当添加第一个元素时会扩容到DEFAULT_CAPACITY
*
* 使用transient关键字修饰,表明该数组不会被默认序列化机制序列化
* (通过自定义writeObject方法优化序列化性能,只序列化实际元素)
* 访问修饰符不是private,是为了简化嵌套类的访问
*/
transient Object[] elementData;
/**
* ArrayList中实际包含的元素数量(不是底层数组的容量)
* @serial 表明该字段会被序列化
*/
private int size;
}add() 方法用来添加元素到 ArrayList 中,如果容量不够,则会进行扩容操作。下面就通过源码来了解一下:
/**
* 将指定元素追加到列表的末尾
*
* @param e 要添加到列表中的元素
* @return {@code true}(按照{@link Collection#add}的规范)
*/
public boolean add(E e) {
// 在 AbstractList 中进行定义
// 记录修改次数,用于迭代器的快速失败机制
modCount++;
// 调用辅助方法执行实际的添加操作
add(e, elementData, size);
return true;
}add(e, elementData, size) 方法定义如下:
/**
* 从add(E)方法中拆分出来的辅助方法,用于将方法字节码大小控制在35字节以内
* (-XX:MaxInlineSize 的默认值),这在 add(E) 方法被C1编译的循环调用时有助于性能优化
*
* @param e 要添加的元素
* @param elementData 存储元素的底层数组
* @param s 当前列表中的元素数量(即下一个要添加元素的索引位置)
*/
private void add(E e, Object[] elementData, int s) {
// 如果当前元素数量等于数组长度,说明数组已满,需要扩容
if (s == elementData.length)
elementData = grow();
// 将元素添加到数组的指定位置(当前元素数量对应的索引)
elementData[s] = e;
// 元素数量加1
size = s + 1;
}扩容操作:
/**
* 扩容方法的简化版本,默认按照当前元素数量+1的需求进行扩容
* @return 扩容后的新数组
*/
private Object[] grow() {
// 调用带参的 grow 方法,最小容量为当前元素数量+1
return grow(size + 1);
}
/**
* 增加容量以确保列表至少能容纳指定的最小容量的元素
*
* @param minCapacity 期望的最小容量
* @throws OutOfMemoryError 如果 minCapacity 为负数(表示容量溢出)
*/
private Object[] grow(int minCapacity) {
// 获取当前底层数组的容量
int oldCapacity = elementData.length;
// 处理非默认空数组的情况(包括已初始化容量或用户指定容量为0的情况)
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 计算新容量:使用 ArraysSupport 工具类计算,遵循以下规则:
// 1. 最小增长为 (minCapacity - oldCapacity),确保至少满足最小容量需求
// 2. 首选增长为 oldCapacity >> 1(即旧容量的一半),实现 1.5 倍左右的扩容
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* 最小增长量 */
oldCapacity >> 1 /* 首选增长量 */);
// 复制旧数组元素到新容量的数组中,并更新底层数组引用
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
// 处理默认空数组的情况(使用无参构造创建的列表)
// 首次扩容时,容量取默认容量(10)和最小需求容量中的较大值
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}get() 方法用于从 ArrayList 获取一个元素,实现如下:
/**
* 返回列表中指定位置的元素
*
* @param index 要返回元素的索引位置
* @return 列表中指定索引位置的元素
* @throws IndexOutOfBoundsException 如果索引超出范围(index < 0 或 index >= size())
*/
public E get(int index) {
// 检查索引的有效性,若索引越界则抛出 IndexOutOfBoundsException
Objects.checkIndex(index, size);
// 调用 elementData 方法获取指定索引处的元素
return elementData(index);
}
/**
* 从底层数组中获取指定索引处的元素,并进行类型转换
*
* @param index 要获取元素的索引位置(已确保在有效范围内)
* @return 转换为泛型E类型的指定索引处的元素
*/
@SuppressWarnings("unchecked") // 抑制 unchecked 类型转换的警告,因为底层数组存储的是E类型元素
E elementData(int index) {
// 从底层数组elementData中获取指定索引的元素,并强制转换为泛型E类型
return (E) elementData[index];
}/**
* 移除列表中指定索引位置的元素
* 后续所有元素会向左移动一位(即它们的索引值减1)
*
* @param index 要移除元素的索引位置
* @return 从列表中被移除的元素
* @throws IndexOutOfBoundsException 如果索引超出范围(index < 0 或 index >= size())
*/
public E remove(int index) {
// 1. 检查索引有效性:若索引越界,直接抛出IndexOutOfBoundsException
Objects.checkIndex(index, size);
// 2. 缓存底层数组引用(避免多次访问成员变量,提升效率)
final Object[] es = elementData;
// 3. 取出要移除的元素并强制转换为泛型E类型(抑制unchecked警告,
// 因底层数组存储的是E类型元素)
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
// 4. 调用私有快速移除方法,执行实际的元素移除和数组移位逻辑
fastRemove(es, index);
// 5. 返回被移除的元素
return oldValue;
}
/**
* 私有移除方法:跳过索引边界检查(前提是外部调用已确保索引合法),且不返回被移除的元素
* 用于内部高效移除元素,避免重复边界检查和返回值处理的开销
*
* @param es 存储元素的底层数组(已通过外部传入,避免重复获取成员变量)
* @param i 要移除元素的索引位置(已确保合法,无需二次检查)
*/
private void fastRemove(Object[] es, int i) {
// 1. 增加修改计数器:用于迭代器的快速失败(fail-fast)机制,标记列表结构已变更
modCount++;
// 2. 计算移除元素后的新元素数量(原数量-1)
final int newSize;
// 3. 判断被移除元素是否为列表的最后一个元素:
// - 若 newSize > i(即移除的不是最后一个元素),则需要将后续元素向左移位
if ((newSize = size - 1) > i)
// 调用系统级数组复制方法:将 es 中从 i+1 位置开始的元素,复制到 es 中从i位置开始的区域
// 复制长度为 (newSize - i),即后续需要移位的元素总数
System.arraycopy(es, i + 1, es, i, newSize - i);
// 4. 清空最后一个元素的引用(原size位置,现在已变为newSize),帮助GC回收内存
// 同时更新列表的实际元素数量size为newSize
es[size = newSize] = null;
}更多源码请查看 ArrayList 源码。