ArrayList 是 Java 集合框架中非常核心和常用的一种数据结构,理解它对于进行 Java 开发至关重要。

什么是 ArrayList?
ArrayList 的本质是一个动态数组,它实现了 List 接口,可以看作是长度可变的数组。
核心特点:
- 有序性:元素的存储顺序和插入顺序一致。
- 可重复性:可以存储重复的元素。
- 动态扩容:可以在运行时动态地增加或减少其大小,这是它与普通数组(
[])最大的区别。
为什么使用 ArrayList 而不是普通数组?
| 特性 | 普通数组 (int[]) |
ArrayList |
|---|---|---|
| 长度 | 固定,创建时必须指定长度,之后无法改变。 | 动态可变,可以随时添加或删除元素。 |
| 数据类型 | 可以是基本类型(int, char 等)或对象。 |
只能存储对象,对于基本类型,需要使用其对应的包装类(如 Integer, Character)。 |
| 性能 | 访问(随机访问)速度极快,直接通过索引计算内存地址。 | 访问速度也很快(底层是数组),但比普通数组稍慢一点,增删中间元素时性能较差。 |
| 功能 | 功能非常有限,就是一个容器。 | 提供了丰富的 API,如 add(), remove(), size(), contains() 等,操作非常方便。 |
当你需要一个固定大小的、高性能的容器时,使用普通数组,当你需要一个大小不固定、需要频繁增删元素、且希望使用丰富方法的容器时,ArrayList 是不二之选。
如何使用 ArrayList (核心 API)
1 创建 ArrayList
import java.util.ArrayList; import java.util.List; // 1. 创建一个可以存储 String 类型的 ArrayList ArrayList<String> stringList = new ArrayList<>(); // 2. 创建一个可以存储 Integer 类型的 ArrayList (推荐使用 List 接口作为引用类型) List<Integer> integerList = new ArrayList<>(); // 3. 创建时指定初始容量 (可选) // 如果大致知道要存储多少元素,指定初始容量可以减少扩容操作,提高性能 List<Double> doubleList = new ArrayList<>(20);
2 常用方法
import java.util.ArrayList;
import java.util.List;
public class ArrayListExample {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
// 1. 添加元素
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
System.out.println("添加后: " + fruits); // [Apple, Banana, Orange]
// 2. 在指定位置插入元素
fruits.add(1, "Mango");
System.out.println("在索引1插入Mango后: " + fruits); // [Apple, Mango, Banana, Orange]
// 3. 获取元素
String firstFruit = fruits.get(0);
System.out.println("第一个水果是: " + firstFruit); // Apple
// 4. 获取列表大小(元素个数)
int size = fruits.size();
System.out.println("列表大小: " + size); // 4
// 5. 删除元素
// 5.1 删除指定位置的元素
fruits.remove(2); // 删除 "Banana"
System.out.println("删除索引2的元素后: " + fruits); // [Apple, Mango, Orange]
// 5.2 删除指定的元素(第一个匹配的)
fruits.remove("Apple");
System.out.println("删除元素'Apple'后: " + fruits); // [Mango, Orange]
// 6. 修改元素
fruits.set(0, "Pineapple");
System.out.println("修改索引0的元素后: " + fruits); // [Pineapple, Orange]
// 7. 判断元素是否存在
boolean hasOrange = fruits.contains("Orange");
System.out.println("列表中是否包含'Orange'? " + hasOrange); // true
// 8. 清空列表
fruits.clear();
System.out.println("清空列表后: " + fruits); // []
System.out.println("列表是否为空? " + fruits.isEmpty()); // true
}
}
ArrayList 的底层原理 (面试重点)
理解 ArrayList 的底层实现,能帮助你更好地使用它并写出高性能的代码。

1 核心属性
ArrayList 内部主要由两个属性构成:
// 1. 存储元素的数组, transient 表示该属性不会被序列化 private transient Object[] elementData; // 2. ArrayList 中当前存储的元素个数 private int size;
elementData:这是一个Object类型的数组,真正用来存放数据的地方。size:记录当前有多少个元素,它不一定是数组的长度。
2 动态扩容机制
ArrayList 的“动态”主要体现在其自动扩容机制上。
-
初始容量:当你使用
new ArrayList<>()无参构造函数时,elementData是一个空数组(长度为0),当你第一次调用add()方法时,它会将elementData的容量扩容到DEFAULT_CAPACITY(默认为10)。 -
扩容时机:当
add()一个元素时,size已经等于elementData.length(即数组已满),就会触发扩容。 -
扩容方式:创建一个新的、更大的数组,然后将旧数组中的所有元素复制到新数组中,最后让
elementData引用这个新数组。 -
扩容大小:新的容量通常是旧容量的1.5倍(使用位运算
oldCapacity + (oldCapacity >> 1)实现)。
// 伪代码演示扩容过程
public void add(E e) {
// 1. 检查是否需要扩容
if (size == elementData.length) {
// 计算新容量
int newCapacity = elementData.length + (elementData.length >> 1);
// 创建新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 2. 添加元素
elementData[size++] = e;
}
扩容的代价:扩容是一个比较耗时的操作,因为它需要创建一个新数组并复制所有元素,如果你能预估到 ArrayList 将要存储的元素数量,在创建时指定一个合适的初始容量,可以避免频繁扩容,提升性能。
遍历 ArrayList
有四种常见的遍历方式:
List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));
// 1. for 循环 + 索引 (最传统,适用于需要索引的场景)
System.out.println("--- for 循环 ---");
for (int i = 0; i < names.size(); i++) {
System.out.println(names.get(i));
}
// 2. 增强 for 循环 (最常用,代码简洁)
System.out.println("\n--- 增强 for 循环 ---");
for (String name : names) {
System.out.println(name);
}
// 3. 使用 Iterator (迭代器),适合在遍历时进行删除操作
System.out.println("\n--- 使用 Iterator ---");
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
if ("Bob".equals(name)) {
iterator.remove(); // 安全删除
}
}
System.out.println("删除Bob后: " + names); // [Alice, Charlie]
// 4. 使用 Java 8+ 的 Stream API (函数式风格,功能强大)
System.out.println("\n--- 使用 Stream API ---");
names.stream().forEach(System.out::println);
线程安全问题
ArrayList 是非线程安全的,如果在多线程环境下,多个线程同时修改一个 ArrayList 实例,可能会导致数据不一致或 ConcurrentModificationException(并发修改异常)。
如何解决?
-
使用
Collections.synchronizedList():将ArrayList包装成线程安全的列表。List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
-
使用
CopyOnWriteArrayList:这是java.util.concurrent包下的一个类,它是一种“写时复制”的并发列表,适用于读多写少的场景,每次修改操作(add, set, remove)都会复制一份底层数组,保证了线程安全,但写入性能较差。
| 特性 | 描述 |
|---|---|
| 数据结构 | 基于动态数组的 List 接口实现。 |
| 核心优点 | 随机访问快(get() 方法效率高),增删末尾元素快。 |
| 核心缺点 | 在中间位置增删元素慢(需要移动大量元素),扩容有性能开销。 |
| 线程安全 | 非线程安全。 |
| 初始容量 | 默认为10,第一次 add 时初始化。 |
| 扩容机制 | 容量不足时,扩容为原容量的 5 倍。 |
| 适用场景 | 需要频繁随机访问元素,且增删操作主要集中在列表末尾的场景。 |
掌握 ArrayList 是 Java 编程的基础,理解其原理能让你在开发中做出更合理的技术选型,并写出更高效的代码。
