Java ArrayList 详解
ArrayList
是 Java 集合框架(Collection Framework)中最常用的类之一,是一种基于动态数组的数据结构,属于 List 接口的实现类。它允许存储重复的元素,有序,支持随机访问,且动态扩容。
1. ArrayList 的特点
- 有序:
ArrayList
按照元素插入的顺序存储,并按索引位置访问。 - 允许重复元素:可以存储重复值。
- 动态扩容:当容量不足时,会自动扩展存储空间。
- 随机访问:通过索引快速访问元素(时间复杂度 O(1))。
- 线程不安全:
ArrayList
不是线程安全的,多个线程同时修改需要手动同步。 - 实现接口:实现了 List 接口,同时支持 RandomAccess、Cloneable 和 Serializable。
2. ArrayList 的基本用法
2.1 创建与初始化
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
// 创建空的 ArrayList
ArrayList<String> list = new ArrayList<>();
// 添加元素
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 输出列表
System.out.println("List: " + list); // 输出: [Apple, Banana, Cherry]
}
}
2.2 常用方法
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
// 添加元素
list.add("A");
list.add("B");
list.add("C");
// 获取元素
System.out.println("Element at index 1: " + list.get(1)); // 输出: B
// 修改元素
list.set(1, "Z");
System.out.println("After update: " + list); // 输出: [A, Z, C]
// 删除元素
list.remove("A");
System.out.println("After removal: " + list); // 输出: [Z, C]
// 检查是否包含
System.out.println("Contains B? " + list.contains("B")); // 输出: false
// 遍历
for (String s : list) {
System.out.println(s);
}
// 清空列表
list.clear();
System.out.println("Is empty? " + list.isEmpty()); // 输出: true
}
}
3. ArrayList 的常用操作方法
方法 | 描述 |
---|---|
add(E e) | 在列表末尾添加元素。 |
add(int index, E element) | 在指定位置插入元素,原位置及后续元素右移。 |
remove(Object o) | 删除首次出现的指定元素。 |
remove(int index) | 删除指定索引位置的元素。 |
get(int index) | 获取指定索引位置的元素。 |
set(int index, E element) | 替换指定索引位置的元素为新的元素。 |
size() | 返回列表中元素的数量。 |
contains(Object o) | 检查列表中是否包含指定元素。 |
indexOf(Object o) | 返回元素的首次出现位置,若不存在则返回 -1。 |
lastIndexOf(Object o) | 返回元素最后一次出现的位置。 |
clear() | 清空列表中的所有元素。 |
isEmpty() | 检查列表是否为空。 |
toArray() | 将列表转换为数组。 |
addAll(Collection<? extends E>) | 添加另一个集合的所有元素到列表。 |
sort(Comparator<? super E>) | 对列表进行排序。 |
4. ArrayList 的工作原理
4.1 动态数组实现
- 初始容量:创建时,如果未指定容量,
ArrayList
默认初始容量为 10。 - 扩容机制:当存储的元素数量超过当前容量时,
ArrayList
会动态扩容。扩容后的新容量为 原容量的 1.5 倍。 - 实现细节:底层通过数组
Object[] elementData
来存储元素。
扩容的实现代码:
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量 = 原容量 + 原容量的一半
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
4.2 随机访问
- 通过数组的索引访问,效率高,时间复杂度为 O(1)。
4.3 插入和删除
- 插入或删除会导致后续元素的移动,时间复杂度为 O(n)。
5. ArrayList 的优缺点
5.1 优点
- 随机访问效率高:支持通过索引快速访问元素。
- 动态扩容:容量不足时自动扩展,无需手动调整。
- 实现了丰富的操作方法:提供增删改查、排序等常用操作。
5.2 缺点
- 插入和删除效率低:需要移动后续元素,尤其是在中间位置操作时。
- 线程不安全:在多线程环境中需要额外同步机制。
- 扩容成本高:扩容时需要分配新数组并拷贝旧数组的元素。
6. 线程安全的替代方案
6.1 Vector
Vector
是线程安全的ArrayList
替代方案,但由于每个方法都进行同步,性能较低。
6.2 Collections.synchronizedList
- 可以通过
Collections.synchronizedList
包装一个线程安全的ArrayList
。
import java.util.*;
public class SynchronizedListExample {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
list.add("A");
list.add("B");
synchronized (list) {
for (String s : list) {
System.out.println(s);
}
}
}
}
6.3 CopyOnWriteArrayList
- 在多线程环境中使用
CopyOnWriteArrayList
,每次写操作都会创建新的副本,适用于读多写少的场景。
7. 常见问题与注意事项
-
越界异常:
- 尝试访问不存在的索引位置会抛出
IndexOutOfBoundsException
。
list.get(10); // 若列表长度不足,抛异常
- 尝试访问不存在的索引位置会抛出
-
性能问题:
- 插入、删除操作在列表越靠后的位置,性能越高。
- 遍历时,建议使用增强型
for
循环或迭代器。
-
多线程环境:
ArrayList
是非线程安全的,多线程操作时需显式同步。
8. 总结
特性 | 描述 |
---|---|
实现结构 | 动态数组,支持随机访问,扩容按 1.5 倍增长。 |
线程安全性 | 非线程安全,需要显式同步或使用线程安全的替代方案。 |
性能 | 读取效率高(O(1)),插入和删除效率低(O(n))。 |
适用场景 | 适合需要频繁读取元素的场景,不适合高频插入或删除场景。 |
ArrayList
是 Java 中灵活且高效的动态数组实现,在开发中广泛应用于需要存储有序、可重复数据的场景。理解其特性和工作原理,合理使用它,能够显著提升程序的性能和可维护性。