- 什么是
ArrayList? - 如何使用
ArrayList(核心方法) ArrayList的内部工作原理(扩容机制)ArrayList与数组的区别ArrayList与LinkedList的区别ArrayList的线程安全问题
什么是 ArrayList?
ArrayList 是 Java 集合框架中的一部分,位于 java.util 包下,它的全称是 "Array List",即 “数组列表”。

它的核心特点是:
- 动态大小:它是一个可以动态改变长度的数组,普通 Java 数组在创建时大小就固定了,而
ArrayList可以根据需要自动增长或缩小。 - 基于数组:尽管它叫“列表”,但其底层实现是一个动态数组。
- 有序:它保持元素的插入顺序。
- 允许重复元素:可以存储多个相同的对象。
- 允许 null 元素:可以存储
null值。
你可以把它想象成一个“超级数组”,它既有数组快速随机访问的优点,又解决了数组大小固定的缺点。
如何使用 ArrayList(核心方法)
在使用 ArrayList 之前,你需要导入它。
import java.util.ArrayList;
1 创建 ArrayList
ArrayList 是一个泛型类,所以在创建时需要指定它可以存储的对象类型。

// 1. 创建一个可以存储 String 类型的 ArrayList ArrayList<String> stringList = new ArrayList<>(); // 2. 创建一个可以存储 Integer 类型的 ArrayList ArrayList<Integer> numberList = new ArrayList<>(); // 3. 创建时指定初始容量 (可选) // 如果大致知道要存多少元素,指定初始容量可以提高性能,避免多次扩容 ArrayList<Double> doubleList = new ArrayList<>(20);
2 常用方法
| 方法 | 描述 | 示例 |
|---|---|---|
add(E element) |
在列表末尾添加一个元素。 | list.add("Apple"); |
add(int index, E element) |
在指定位置插入一个元素。 | list.add(0, "Banana"); |
get(int index) |
获取指定位置的元素。 | String fruit = list.get(0); |
set(int index, E element) |
替换指定位置的元素。 | list.set(0, "Orange"); |
remove(int index) |
移除指定位置的元素。 | list.remove(0); |
remove(Object o) |
移除第一个匹配的元素。 | list.remove("Orange"); |
size() |
返回列表中的元素数量。 | int count = list.size(); |
isEmpty() |
如果列表为空,返回 true。 |
boolean empty = list.isEmpty(); |
contains(Object o) |
如果列表包含指定元素,返回 true。 |
boolean hasApple = list.contains("Apple"); |
clear() |
清空列表中的所有元素。 | list.clear(); |
toArray() |
将列表转换为数组。 | String[] array = list.toArray(new String[0]); |
3 完整示例
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
// 1. 创建 ArrayList
ArrayList<String> fruits = new ArrayList<>();
// 2. 添加元素
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
System.out.println("初始列表: " + fruits); // 输出: [Apple, Banana, Cherry]
// 3. 在指定位置插入
fruits.add(1, "Blueberry");
System.out.println("插入后: " + fruits); // 输出: [Apple, Blueberry, Banana, Cherry]
// 4. 获取元素
String firstFruit = fruits.get(0);
System.out.println("第一个水果是: " + firstFruit); // 输出: Apple
// 5. 替换元素
fruits.set(2, "Blackberry");
System.out.println("替换后: " + fruits); // 输出: [Apple, Blueberry, Blackberry, Cherry]
// 6. 移除元素 (按索引)
fruits.remove(0);
System.out.println("移除第一个后: " + fruits); // 输出: [Blueberry, Blackberry, Cherry]
// 7. 移除元素 (按对象)
fruits.remove("Cherry");
System.out.println("移除Cherry后: " + fruits); // 输出: [Blueberry, Blackberry]
// 8. 获取大小
System.out.println("列表大小: " + fruits.size()); // 输出: 2
// 9. 检查是否包含某个元素
System.out.println("列表是否包含'Blueberry'? " + fruits.contains("Blueberry")); // 输出: true
// 10. 清空列表
fruits.clear();
System.out.println("清空后的列表: " + fruits); // 输出: []
System.out.println("列表是否为空? " + fruits.isEmpty()); // 输出: true
}
}
ArrayList 的内部工作原理(扩容机制)
这是理解 ArrayList 性能的关键。
ArrayList 内部维护一个数组,我们称之为 elementData,当你创建一个 ArrayList 时,这个 elementData 就被初始化了。
- 初始容量:如果你没有指定初始容量(
new ArrayList<>()),默认的初始容量是 10。 - 扩容:当你添加的元素数量超过了
elementData的当前长度时,ArrayList就需要扩容,它会创建一个新的、更大的数组,然后将旧数组中的所有元素复制到新数组中,最后在新数组中添加新元素。
扩容的大小是多少?
在 Java 8 及更高版本中,当 ArrayList 需要扩容时,新的容量是 旧容量的 1.5 倍(oldCapacity + (oldCapacity >> 1)),一个容量为 10 的数组在扩容后会变成一个容量为 15 的数组。
为什么扩容会影响性能?
因为扩容操作(创建新数组 + 复制所有旧元素)是一个比较耗时的操作,时间复杂度是 O(n),如果你能预估 ArrayList 的大致大小,在创建时指定一个合适的初始容量,可以有效避免频繁扩容,从而提高性能。

ArrayList 与数组的区别
| 特性 | 数组 (String[]) |
ArrayList<String> |
|---|---|---|
| 大小 | 固定,创建后不能改变。 | 动态,可以自动增长和缩小。 |
| 性能 | 随机访问快 (O(1)),增删慢 (需要移动元素)。 | 随机访问快 (O(1)),末尾增删快,中间增删慢 (需要移动元素)。 |
| 功能 | 功能简单,只有 length 属性。 |
功能丰富,提供大量方法 (add, remove, size 等)。 |
| 类型 | 可以存储基本类型 (int[], char[]) 和对象。 |
只能存储对象,对于基本类型,需要使用其包装类 (Integer, Character)。 |
| 语法 | String[] arr = new String[5]; |
ArrayList<String> list = new ArrayList<>(); |
ArrayList 与 LinkedList 的区别
LinkedList 是 ArrayList 的另一个常见实现,它们都实现了 List 接口,但底层结构和性能特点完全不同。
| 特性 | ArrayList |
LinkedList |
|---|---|---|
| 底层结构 | 动态数组。 | 双向链表,每个节点存储数据、前驱节点和后继节点的引用。 |
| 随机访问 | 非常快 (O(1)),直接通过索引计算内存地址。 | 慢 (O(n)),需要从头或尾开始遍历链表。 |
| 中间增删 | 慢 (O(n)),需要移动被删除元素之后的所有元素。 | 快 (O(1)),只需要修改目标节点及其前后节点的引用即可。 |
| 内存占用 | 较低,只有数组本身的开销。 | 较高,每个元素除了数据外,还需要存储两个引用(前后节点)。 |
| 使用场景 | 读多写少的场景,存储大量数据后需要频繁查询。 | 写多读少的场景,需要频繁在中间插入或删除元素。 |
ArrayList 的线程安全问题
ArrayList 是非线程安全的。
这意味着,如果在多线程环境下,多个线程同时修改同一个 ArrayList 实例,可能会导致数据不一致或损坏。
如何解决?
- 使用
Vector:Vector是ArrayList的一个线程安全的“老版本”,它的所有方法都使用了synchronized关键字,性能较差,不推荐在新代码中使用。 - 使用
Collections.synchronizedList():可以将一个ArrayList包装成线程安全的列表。List<String> list = Collections.synchronizedList(new ArrayList<>());
在遍历时,仍然需要手动同步,否则可能抛出
ConcurrentModificationException。synchronized (list) { for (String item : list) { // ... } } - 使用
CopyOnWriteArrayList:这是 Java 并发包 (java.util.concurrent) 中的一个类,它是一种“写时复制”的列表,适用于读远多于写的场景,它的读操作不加锁,性能很高;写操作时,会复制整个底层数组,在副本上进行修改,然后替换掉旧数组,这保证了线程安全,但写操作的开销很大。 - 使用
ConcurrentHashMap的KeySet:如果只是需要一个线程安全的、不重复的元素集合,可以考虑new ConcurrentHashMap<>().newKeySet()。
ArrayList是什么? 一个基于动态数组的、大小可变的列表实现。- 核心优势? 随机访问速度快(O(1)),末尾增删效率高。
- 核心劣势? 中间增删效率低(O(n)),非线程安全。
- 何时使用?
- 当你需要一个可以动态调整大小的列表时。
- 当你的应用场景是读多写少时。
- 当你不需要线程安全,或者会自己处理线程同步时。
- 关键点? 了解其扩容机制,以便在性能敏感的场景下预估容量。
