ArrayList 和 Vector 是 Java 中的两个列表类,它们都基于动态数组实现,并且都实现了 List 接口,因此在功能上有很多相似之处。但它们的设计侧重点不同,主要区别在于线程安全性和性能问题。
1. 线程安全性
-
ArrayList:
- 不是线程安全的。
- 如果多个线程同时对
ArrayList进行读写操作(比如添加、删除元素),可能会导致数据不一致或程序崩溃。 - 如果需要并发安全操作,通常需要手动加锁,或者使用
Collections.synchronizedList()方法将其转换为线程安全的版本。
-
Vector:
- 线程安全的。
Vector的方法(如add、remove等)都使用了同步关键字(synchronized)进行内部加锁,确保线程安全。- 这意味着多个线程可以同时操作一个
Vector而不会出现数据一致性问题。
如果需要在多线程环境下使用,Vector 是线程安全的,而 ArrayList 需要额外处理线程安全性。
2. 性能
-
ArrayList:
- 因为没有同步机制,操作效率更高。
- 适合在单线程或不需要线程安全的场景中使用。
-
Vector:
- 因为每个方法都加了同步锁,性能会比
ArrayList慢,即使在单线程环境下,它的同步机制也会导致不必要的性能开销。
- 因为每个方法都加了同步锁,性能会比
在单线程环境中,ArrayList 的性能优于 Vector,更推荐使用 ArrayList。
3. 扩容机制
-
ArrayList:
- 当数组空间不足时,按 1.5 倍的大小进行扩容(即新容量 = 当前容量 × 1.5)。
- 这种扩容方式节省了一定的内存,但可能会导致扩容次数稍微多一些。
-
Vector:
- 当数组空间不足时,按 2 倍的大小进行扩容(即新容量 = 当前容量 × 2)。
- 扩容倍数更大,相对减少了扩容次数,但可能会浪费更多内存。
ArrayList 的扩容机制更加节约内存,而 Vector 的扩容机制可能会浪费一些内存,但适合需要频繁扩容的场景。
4. 迭代器的使用
-
ArrayList:
- 推荐使用
Iterator或for-each循环来遍历。 - 如果使用的是
Iterator,同时有其他线程修改了列表,会抛出ConcurrentModificationException(因为ArrayList没有内置的线程安全机制)。
- 推荐使用
-
Vector:
- 除了可以使用
Iterator和for-each,还支持一种过时的遍历方式:Enumeration。 Enumeration是Vector中的旧式迭代器,早于Iterator出现,不支持快速失败机制(即使其他线程修改了Vector,也不会抛出异常)。
- 除了可以使用
Iterator 是现代推荐的遍历方式,而 Enumeration 是过时的工具,通常不推荐使用。
5. 兼容性
-
ArrayList:
- 是 Java 1.2 中引入的,属于 Java 集合框架的一部分。
- 更现代化,设计更符合目前 Java 的编程习惯。
-
Vector:
- 是 Java 1.0 中引入的,属于早期 Java 的类。
- 虽然它在 Java 集合框架中得到了改造,但很多设计上显得有些“过时”,不推荐在新项目中使用。
ArrayList 是现代化的选择,而 Vector 更多是为了向后兼容早期代码。
6. 使用场景总结
-
ArrayList:
- 单线程环境。
- 对性能要求较高。
- 不需要线程安全。
- 适合现代开发中绝大多数场景。
-
Vector:
- 多线程环境,但对性能要求不高。
- 需要线程安全,但不想手动加锁。
- 需要兼容旧代码(比如早期使用
Vector的项目)。
7. 对比
| 特点 | ArrayList | Vector |
|---|---|---|
| 线程安全性 | 不是线程安全 | 是线程安全 |
| 性能 | 更快(没有同步开销) | 较慢(同步开销较高) |
| 扩容机制 | 容量不足时扩容为原来的 1.5 倍 | 容量不足时扩容为原来的 2 倍 |
| 迭代器 | 推荐使用 Iterator 或 for-each | 支持 Iterator 和过时的 Enumeration |
| 引入时间 | Java 1.2 | Java 1.0 |
| 使用场景 | 单线程环境,性能要求高 | 多线程环境,需要线程安全 |
1. ArrayList 示例
ArrayList 是非线程安全的,因此适合单线程或不需要线程安全的场景。
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
// 创建一个 ArrayList
ArrayList<String> arrayList = new ArrayList<>();
// 添加元素
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add("Cherry");
// 遍历元素
System.out.println("ArrayList 遍历 (for-each):");
for (String item : arrayList) {
System.out.println(item);
}
// 按索引访问元素
System.out.println("\n访问索引 1 的元素: " + arrayList.get(1));
// 删除元素
arrayList.remove("Banana");
System.out.println("\n删除 \"Banana\" 后的 ArrayList: " + arrayList);
}
}
运行结果:
ArrayList 遍历 (for-each):
Apple
Banana
Cherry
访问索引 1 的元素: Banana
删除 "Banana" 后的 ArrayList: [Apple, Cherry]
2. Vector 示例
Vector 是线程安全的,适合需要线程安全的多线程场景,但性能较低。
import java.util.Vector;
public class VectorExample {
public static void main(String[] args) {
// 创建一个 Vector
Vector<String> vector = new Vector<>();
// 添加元素
vector.add("Dog");
vector.add("Cat");
vector.add("Bird");
// 遍历元素 (支持 Enumeration)
System.out.println("Vector 遍历 (Enumeration):");
java.util.Enumeration<String> enumeration = vector.elements();
while (enumeration.hasMoreElements()) {
System.out.println(enumeration.nextElement());
}
// 按索引访问元素
System.out.println("\n访问索引 2 的元素: " + vector.get(2));
// 删除元素
vector.remove("Cat");
System.out.println("\n删除 \"Cat\" 后的 Vector: " + vector);
}
}
运行结果:
Vector 遍历 (Enumeration):
Dog
Cat
Bird
访问索引 2 的元素: Bird
删除 "Cat" 后的 Vector: [Dog, Bird]
3. ArrayList 在多线程中的问题
ArrayList 是非线程安全的,在多线程场景下直接使用可能导致数据不一致或抛出异常。
import java.util.ArrayList;
public class ArrayListMultiThreadIssue {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
// 创建线程1:向列表中添加元素
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
list.add(i);
}
});
// 创建线程2:遍历列表元素
Thread t2 = new Thread(() -> {
for (Integer num : list) {
System.out.println(num);
}
});
t1.start();
t2.start();
}
}
可能出现的问题:
ArrayList的内部结构在多个线程中同时被修改,可能导致崩溃或抛出ConcurrentModificationException。
4. Vector 在多线程中的表现
由于 Vector 的方法是同步的,它可以在多线程环境中安全使用。
import java.util.Vector;
public class VectorMultiThreadSafe {
public static void main(String[] args) {
Vector<Integer> vector = new Vector<>();
// 创建线程1:向 Vector 中添加元素
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
vector.add(i);
}
});
// 创建线程2:遍历 Vector 中的元素
Thread t2 = new Thread(() -> {
for (Integer num : vector) {
System.out.println(num);
}
});
t1.start();
t2.start();
}
}
运行结果:
Vector 在多线程环境下不会抛出异常或导致数据不一致,因为它的操作是线程安全的。
5. 手动同步 ArrayList
如果您希望在多线程中使用 ArrayList,可以通过显式加锁实现线程安全。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SynchronizedArrayListExample {
public static void main(String[] args) {
// 使用 Collections.synchronizedList 方法将 ArrayList 转为线程安全
List<Integer> synchronizedList = Collections.synchronizedList(new ArrayList<>());
// 创建线程1:向列表中添加元素
Thread t1 = new Thread(() -> {
synchronized (synchronizedList) { // 手动同步块
for (int i = 0; i < 1000; i++) {
synchronizedList.add(i);
}
}
});
// 创建线程2:遍历列表中的元素
Thread t2 = new Thread(() -> {
synchronized (synchronizedList) { // 手动同步块
for (Integer num : synchronizedList) {
System.out.println(num);
}
}
});
t1.start();
t2.start();
}
}
注意事项:
- 使用
Collections.synchronizedList可以让列表本身线程安全,但在遍历时仍需要手动加锁以避免并发问题。
总结
- 如果您在开发中需要一个动态数组,并且不涉及多线程,优先选择
ArrayList。 - 如果您需要线程安全的列表,又不想手动加锁,可以考虑
Vector,但更推荐使用CopyOnWriteArrayList(性能更优的线程安全列表)。 - Vector 是一个过时的类,更多是为了向后兼容,建议在现代开发中避免使用。