什么是 ArrayList?
ArrayList 是一个动态数组,它实现了 List 接口,可以看作是数组的增强版,与普通数组相比,ArrayList 的最大优势在于其大小可以动态改变,你不需要在创建时就指定它的容量,它会在元素添加或删除时自动调整。

核心特点:
- 有序性:
ArrayList中的元素保持插入时的顺序。 - 可重复性: 允许存储重复的元素。
- 非线程安全: 它不是同步的,如果在多线程环境下使用,需要手动进行同步处理(使用
Collections.synchronizedList(new ArrayList<>())或CopyOnWriteArrayList)。 - 基于数组: 它的底层实现是一个对象数组(
Object[])。
为什么使用 ArrayList?(相比普通数组)
| 特性 | 普通数组 | ArrayList |
|---|---|---|
| 大小 | 固定,创建时必须指定 | 动态可变 |
| 性能 | 访问(随机访问)快,增删慢 | 访问稍慢(但仍很快),增删也慢(在中间位置) |
| 功能 | 功能简单,只有 length 属性 |
功能丰富,提供大量方法(add, remove, size 等) |
| 数据类型 | 可以是基本类型(int[], double[]) |
只能存储对象(存储基本类型时需使用其包装类,如 Integer) |
当你需要一个可以动态改变大小的列表,并且需要丰富的操作方法时,ArrayList 是比普通数组更好的选择。
ArrayList 的核心方法
下面是 ArrayList 最常用的一些方法:
构造方法
ArrayList(): 创建一个空的列表,初始容量为 10。ArrayList(int initialCapacity): 创建一个具有指定初始容量的空列表。ArrayList(Collection<? extends E> c): 创建一个包含指定集合元素的列表。
常用方法
| 方法 | 描述 | 示例 |
|---|---|---|
| 添加元素 | ||
add(E e) |
将指定的元素追加到此列表的末尾。 | list.add("Apple"); |
add(int index, E element) |
在此列表的指定位置插入指定的元素。 | list.add(1, "Banana"); |
| 删除元素 | ||
remove(Object o) |
移除此列表中首次出现的指定元素(如果存在)。 | list.remove("Apple"); |
remove(int index) |
移除此列表中指定位置的元素。 | list.remove(0); |
| 获取元素 | ||
get(int index) |
返回此列表中指定位置的元素。 | String fruit = list.get(0); |
| 查询信息 | ||
size() |
返回此列表中的元素数。 | int count = list.size(); |
isEmpty() |
如果此列表中没有元素,则返回 true。 |
if (list.isEmpty()) { ... } |
contains(Object o) |
如果此列表包含指定的元素,则返回 true。 |
boolean hasOrange = list.contains("Orange"); |
| 修改元素 | ||
set(int index, E element) |
用指定的元素替换此列表中指定位置的元素。 | list.set(0, "Grape"); |
| 其他 | ||
clear() |
从此列表中移除所有元素。 | list.clear(); |
toArray() |
以正确的顺序返回一个包含此列表中所有元素的数组。 | Object[] arr = list.toArray(); |
ArrayList 的底层原理与性能分析
理解 ArrayList 的底层实现有助于你更好地使用它。

核心属性
ArrayList 内部主要有两个属性:
// 存储 ArrayList 元素的数组 private transient Object[] elementData; // ArrayList 的大小(元素个数) private int size;
transient 关键字表示 elementData 数组不会被序列化。
动态扩容机制
这是 ArrayList 最核心的机制,当你向一个已满的 ArrayList 中添加元素时,它会创建一个更大的新数组,然后将旧数组中的所有元素复制到新数组中,最后再添加新元素。
- 初始容量:默认为 10。
- 扩容触发:当
size达到capacity时,再调用add()方法就会触发扩容。 - 扩容大小:新数组的容量通常是旧容量的 1.5 倍,这个计算在
grow(int minCapacity)方法中完成:int newCapacity = oldCapacity + (oldCapacity >> 1);(右移一位相当于除以2)。 - 性能影响:扩容是一个比较耗时的操作,因为它需要创建新数组和复制元素,如果你能预估出大概需要存储多少元素,最好在创建
ArrayList时指定一个合适的初始容量,以减少扩容带来的性能开销。
// 示例:预估需要存储 1000 个元素 List<String> list = new ArrayList<>(1000); // 避免多次扩容
性能分析
| 操作 | 时间复杂度 | 原因 |
|---|---|---|
add(E e) |
平均 O(1) 最坏情况 O(n) |
平均情况下是添加到末尾,直接赋值即可,但如果触发了扩容,就需要复制整个数组,此时为 O(n)。 |
add(int index, E element) |
O(n) | 需要将 index 位置之后的所有元素都向后移动一位,以腾出空间。 |
remove(int index) |
O(n) | 需要将 index 位置之后的所有元素都向前移动一位,填补空缺。 |
get(int index) |
O(1) | 底层是数组,根据索引直接计算内存地址访问,速度极快,这是 ArrayList 最大的优势。 |
set(int index, E element) |
O(1) | 和 get 类似,直接通过索引找到位置并赋值。 |
contains(Object o) |
O(n) | 需要遍历整个数组,逐个比较元素。 |
ArrayList适合的场景:频繁随机访问(遍历、查询),而不需要在中间频繁插入或删除。ArrayList不适合的场景:频繁地在头部或中间位置插入/删除元素,这时,LinkedList(基于链表,增删快,访问慢)会是更好的选择。
代码示例
import java.util.ArrayList;
import java.util.List;
public class ArrayListExample {
public static void main(String[] args) {
// 1. 创建一个 ArrayList
List<String> fruits = new ArrayList<>();
// 2. 添加元素
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
System.out.println("初始列表: " + fruits); // [Apple, Banana, Orange]
// 3. 在指定位置添加元素
fruits.add(1, "Mango");
System.out.println("在索引1处添加Mango后: " + fruits); // [Apple, Mango, Banana, Orange]
// 4. 获取元素
String firstFruit = fruits.get(0);
System.out.println("第一个水果是: " + firstFruit); // Apple
// 5. 修改元素
fruits.set(2, "Grape");
System.out.println("将索引2处的元素改为Grape后: " + fruits); // [Apple, Mango, Grape, Orange]
// 6. 获取列表大小
System.out.println("列表大小: " + fruits.size()); // 4
// 7. 检查是否包含某个元素
boolean hasBanana = fruits.contains("Banana");
System.out.println("列表中是否包含Banana? " + hasBanana); // false
// 8. 删除元素
fruits.remove("Mango"); // 删除第一个匹配的"Mango"
System.out.println("删除Mango后: " + fruits); // [Apple, Grape, Orange]
fruits.remove(0); // 删除索引为0的元素
System.out.println("删除索引0的元素后: " + fruits); // [Grape, Orange]
// 9. 遍历列表 (多种方式)
System.out.println("--- 遍历列表 ---");
// 方式一:使用 for-each 循环 (推荐)
for (String fruit : fruits) {
System.out.println(fruit);
}
// 输出: Grape, Orange
// 方式二:使用迭代器
// System.out.println("--- 使用迭代器遍历 ---");
// Iterator<String> iterator = fruits.iterator();
// while (iterator.hasNext()) {
// System.out.println(iterator.next());
// }
// 方式三:使用索引 (对于 ArrayList 来说效率很高)
// for (int i = 0; i < fruits.size(); i++) {
// System.out.println(fruits.get(i));
// }
// 10. 清空列表
fruits.clear();
System.out.println("清空后的列表: " + fruits); // []
System.out.println("列表是否为空? " + fruits.isEmpty()); // true
}
}
ArrayList vs. LinkedList (快速对比)
| 特性 | ArrayList |
LinkedList |
|---|---|---|
| 数据结构 | 动态数组 | 双向链表 |
随机访问 get(i) |
O(1) 非常快 | O(n) 需要从头或尾遍历 |
中间插入 add(i, e) |
O(n) 需要移动元素 | O(1) 只需修改前后节点的指针 |
中间删除 remove(i) |
O(n) 需要移动元素 | O(1) 只需修改前后节点的指针 |
| 内存占用 | 较低(只有数组) | 较高(每个节点都存储前后节点的引用) |
| 适用场景 | 频繁查询,少量增删 | 频繁增删,少量查询 |
希望这份详细的讲解能帮助你完全理解 Java 中的 ArrayList!

