ArrayList 是Java 集合框架中最常用的动态数组实现类。它在 java.util.ArrayList 包下,继承自 AbstractList,实现了 List 接口。
核心特点:底层基于数组实现,容量可以自动扩容,支持快速随机访问。
一、核心特点
- 动态扩容 :初始容量默认是 10,存满后会自动扩容为原来的 1.5 倍;
- 有序可重复 :元素存入顺序和取出顺序一致,允许存储重复元素、
null值; - 随机访问快:通过索引访问元素的时间复杂度是 O (1),效率极高;
- 增删慢:中间插入 / 删除元素需要移动后续元素,时间复杂度 O (n);
- 非线程安全 :多线程环境下不建议使用(推荐
CopyOnWriteArrayList)。
二、基础使用
1. 创建ArrayList
java
import java.util.ArrayList;
// 推荐:指定泛型(约束存储的元素类型)
ArrayList<String> list = new ArrayList<>();
2. 常用方法
java
// 1. 添加元素
list.add("Java");
list.add("Python");
list.add(1, "C++"); // 在索引 1 位置插入元素
// 2. 获取元素
String s = list.get(0); // 获取索引 0 的元素
// 3. 修改元素
list.set(1, "Go"); // 把索引 1 的元素改为 Go
// 4. 删除元素
list.remove(0); // 根据索引删除
list.remove("Java"); // 根据元素删除
// 5. 其他常用方法
list.size(); // 获取元素个数
list.isEmpty(); // 判断是否为空
list.contains("Python"); // 判断是否包含某个元素
list.clear(); // 清空集合
3. 遍历 ArrayList
java
// 方式1:for 循环(通过索引,推荐随机访问)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 方式2:增强 for 循环(最常用)
for (String str : list) {
System.out.println(str);
}
// 方式3:迭代器
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
三、ArrayList底层原理
1. 底层数据结构
Object 数组 transient Object[] elementData;
2. 初始容量
- JDK 1.7:默认初始容量 10;
- JDK 1.8+:懒加载,第一次添加元素时才初始化容量为 10;
3. 扩容机制
当元素个数达到数组容量时,自动扩容:新容量 = 旧容量 + 旧容量 >> 1(等价于 1.5 倍);扩容后会用 Arrays.copyOf() 复制原数组元素。
四、如何保证 ArrayList 的线程安全
ArrayList 本身是非线程安全的,多线程同时读写会出现数据错乱、并发修改异常**(ConcurrentModificationException)**。
Java 提供了 3 种标准方案保证线程安全:
1. Collections.synchronizedList ()(简单包装)
这是最快实现线程安全的方式,对 ArrayList 加锁包装。
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// 把普通 ArrayList 变成线程安全
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
特点:
- 简单、一行代码搞定
- 粗粒度锁,并发性能一般
- 遍历时仍需要手动加锁
遍历注意: 必须手动加锁,否则仍会报错。
java
synchronized (safeList) { // 必须加锁
for (String s : safeList) {
System.out.println(s);
}
}
2. 使用 Vector(古老、不推荐)
Vector 是 Java 早期的线程安全集合,所有方法都加 synchronized。
特点:
- 线程安全,但性能差
- 几乎被淘汰,不要在新项目使用
3. CopyOnWriteArrayList(高并发首选)
Java 官方推荐的线程安全 List ,属于 java.util.concurrent 并发包。
java
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
// 最推荐的线程安全 List
List<String> safeList = new CopyOnWriteArrayList<>();
核心原理:
- 写时复制:添加 / 删除时,复制一个新数组操作
- 读不加锁,写加锁
- 适合:读多写少 的高并发场景
优点:
- 完全线程安全
- 遍历不需要加锁
- 高并发下性能远好于 synchronizedList
4. 三种方案对比
| 方案 | 线程安全 | 性能 | 适用场景 |
|---|---|---|---|
| ArrayList | ❌ 不安全 | 最好 | 单线程 |
| Collections.synchronizedList | ✅ 安全 | 一般 | 简单并发、低并发 |
| Vector | ✅ 安全 | 最差 | 老项目、不推荐 |
| CopyOnWriteArrayList | ✅ 安全 | 读高并发优秀 | 读多写少、高并发 |
五、ArrayList 与 普通数组 的区别
| 特性 | 普通数组 | ArrayList |
|---|---|---|
| 容量 | 固定不可变 | 动态自动扩容 |
| 存储类型 | 基本类型 + 引用类型 | 只能存引用类型 |
| 功能 | 简单存取 | 提供丰富的增删改查方法 |
| 使用场景 | 固定长度数据 | 长度不确定、频繁操作 |
注意:ArrayList 不能存 int、char 等基本类型,只能存包装类 Integer、Character。
六、使用注意事项
1. 指定初始容量
如果能预估元素数量,创建时指定容量,减少扩容开销。
2. 线程不安全
多线程下不要直接使用,可使用:
- 日常开发、低并发使用
Collections.synchronizedList() - 高并发、读多写少使用
CopyOnWriteArrayList
3. 遍历删除问题
在 ArrayList 遍历过程中删除元素,只有迭代器(Iterator)的删除方式是完全安全、不会报错的。
直接用 for/增强for 删除都会出问题:
- 普通 for:漏删、下标越界
- 增强 for:ConcurrentModificationException 并发修改异常
用迭代器删除ArrayList元素关键点:
- 必须用
it.remove(),不能用list.remove() - 必须先调用
it.next(),再调用remove() - 不会抛异常,不会漏删,最安全
不能用 list.remove ()的原因:
- 遍历过程中,直接调用集合的删除方法会修改集合结构,导致迭代器检测到 "被修改",直接抛出异常。
迭代器删除原理:
- ① 迭代器会记录集合修改次数
- ② 只有迭代器自己的 remove () 会同步修改次数,不会触发异常
- ③ 外部调用
list.remove()会导致修改次数不匹配 → 报错