Java ArrayList 核心解析
ArrayList 是 Java 集合框架(Java Collections Framework)中最常用的动态数组实现。它提供了灵活的容量管理、便捷的增删改查操作,广泛应用于日常开发中。本文将深入剖析 ArrayList 的底层结构、核心方法源码、性能特点及最佳实践。
一、ArrayList 的概念
ArrayList 基于动态数组实现,底层维护一个 Object[] 数组,elementData 存储元素,同时通过 size 变量记录当前有效元素个数,容量可自动扩容。它实现了 List 接口,具备以下特性:
- 泛型支持:使用时必须实例化,如
ArrayList<Integer>。 - 随机访问:实现 RandomAccess 接口,支持 O(1) 时间复杂度的索引访问。
- 序列化与克隆:实现 Cloneable 和 Serializable 接口,支持对象克隆和序列化传输。
- 线程安全:非线程安全。在单线程环境下使用效率高;多线程场景下建议选用 Vector 或 CopyOnWriteArrayList。
- 底层结构:一段连续的空间,可以自动扩容,是一个动态类型的顺序表。

二、ArrayList 的使用
1. 构造 ArrayList
ArrayList 提供三种构造函数,根据实际场景选择合适的初始化方式有助于减少扩容开销。
| 方法 | 说明 |
|---|---|
ArrayList() | 无参构造,默认初始容量为 0(JDK 8+) |
ArrayList(Collection<? extends E> c) | 利用其他 Collection 构建 ArrayList |
ArrayList(int initialCapacity) | 指定顺序表初始容量 |
1.1 无参构造方法
public static void main(String[] args) {
// 创建一个空的 ArrayList
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println(list);
}
1.2 带有初始容量的构造方法
如果预估数据量较大,建议直接指定初始容量,避免频繁扩容带来的内存拷贝开销。
// 创建一个有初始容量的 ArrayList
ArrayList<Integer> list2 = new ArrayList<>(5);
list2.add(22);
list2.add(33);
list2.add(44);
System.out.println(list2);
1.3 用其他集合类构造 ArrayList
传入的参数必须是实现了 Collection 接口的对象,且类型需兼容。
// 创建一个新的 ArrayList 并复制现有集合的内容
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(22);
list2.add(33);
list2.add(44);
ArrayList<Integer> list3 = new ArrayList<>(list2);
System.out.println(list3);
2. 操作方法
以下是 ArrayList 最常用的核心方法签名及描述:
| 方法签名 | 描述 |
|---|---|
boolean add(E e) | 将元素 e 插入到列表末尾 |
void add(int index, E element) | 将元素 e 插入到指定的 index 位置 |
boolean addAll(Collection<? extends E> c) | 将集合 c 中的所有元素插入到列表末尾 |
E remove(int index) | 删除并返回指定 index 位置的元素 |
boolean remove(Object o) | 删除列表中第一个遇到的元素 o |
E get(int index) | 获取指定 index 位置的元素 |
E set(int index, E element) | 将指定 index 位置的元素替换为 element |
void clear() | 清空列表中的所有元素 |
List<E> subList(int fromIndex, int toIndex) | 返回从 fromIndex 到 toIndex 的子列表 |
2.1 尾插与插入
// 创建一个空的 ArrayList
ArrayList<Integer> list = new ArrayList<>();
// 末尾插入 1
list.add(1);
System.out.println(list);
// 0 位置插入 1,后续元素后移
list.add(0, 1);
System.out.println(list);
2.2 批量添加
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(11);
list.add(111);
ArrayList<Integer> list2 = new ArrayList<>();
list2.addAll(list);
list2.add(1111);
System.out.println(list2);
2.3 删除与截取
注意 remove 方法重载的区别,以及 subList 的视图特性。
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(11);
list.add(111);
// 按索引删除
list.remove(2);
// 按对象删除,注意 Integer 包装类的比较
Integer x = 2;
list.remove(x);
// 截取子列表,左闭右开 [1, 3)
ArrayList<Integer> list3 = new ArrayList<>();
list3.add(1);
list3.add(11);
list3.add(111);
list3.add(1111);
List<Integer> subList = list3.subList(1, 3);
System.out.println(subList);
注意:
subList返回的是原列表的视图,修改子列表会影响原列表,反之亦然。它们共享底层数组地址。
三、ArrayList 的遍历
遍历方式的选择直接影响代码的可读性与性能,特别是在并发修改场景下。
1. for 循环遍历
适用于需要索引的场景,性能较好。
for (int i = 0; i < list.size(); i++) {
Integer a = list.get(i);
System.out.println(a + " ");
}
2. 增强型 for 循环
语法简洁,内部基于迭代器实现,适合只读遍历。
for (Integer x : list) {
System.out.println(x + " ");
}
3. 迭代器
只要实现 Iterable 接口,都可以使用迭代器打印。迭代器允许在遍历时安全地移除元素。
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next() + " ");
}
如果需要从指定位置开始或逆向遍历,可使用 ListIterator。
// 从索引 2 开始正向遍历
ListIterator<Integer> it2 = list.listIterator(2);
while (it2.hasNext()) {
System.out.println(it2.next() + " ");
}
// 逆向遍历
ListIterator<Integer> it = list.listIterator(list.size());
while (it.hasPrevious()) {
System.out.println(it.previous() + " ");
}
四、ArrayList 扩容机制
ArrayList 是一个动态类型的顺序表,在插入元素的过程中会自动扩容。理解这一机制对于优化内存使用至关重要。
当调用不带参数的构造方法时,JDK 8+ 实际上并不会立即分配大小为 10 的数组,而是先分配一个空数组。直到第一次添加元素时,才会触发扩容逻辑,此时默认容量为 10。后续扩容策略通常为原有容量的 1.5 倍。
扩容过程涉及以下步骤:
- 检查是否需要扩容(当前 size + 1 > capacity)。
- 计算新容量(oldCapacity * 1.5 + 1)。
- 使用
Arrays.copyOf创建新数组。 - 将旧数组内容复制到新数组。



总结
ArrayList 是基于动态数组的 List 实现,具有随机访问快、使用便捷的特点,适合大多数读多写少的场景。在实际开发中,了解其底层原理、核心方法行为及扩容机制,能帮助我们在面对大数据量处理时做出更优的性能决策。


