迭代器与 fail-fast
问题
Java 迭代器是什么?fail-fast 和 fail-safe 机制有什么区别?
答案
Iterator 接口
IteratorDemo.java
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
// 使用 Iterator 遍历
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
if ("b".equals(item)) {
it.remove(); // 安全删除当前元素
}
}
// 增强 for 循环底层就是 Iterator
for (String item : list) {
System.out.println(item);
}
fail-fast(快速失败)
java.util 包下的集合(ArrayList、HashMap 等)使用 fail-fast 机制。在迭代过程中,如果检测到集合结构被修改(增加/删除元素),立即抛出 ConcurrentModificationException:
FailFastDemo.java
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
// ❌ 增强 for 循环中修改 → ConcurrentModificationException
for (String item : list) {
if ("b".equals(item)) {
list.remove(item); // 修改了集合结构
}
}
原理:集合内部维护 modCount(修改计数器),每次结构修改时 modCount++。Iterator 创建时记录 expectedModCount = modCount,每次 next() 时检查两者是否一致:
ArrayList.Itr(简化)
private class Itr implements Iterator<E> {
int expectedModCount = modCount; // 创建时记录
public E next() {
checkForComodification(); // 检查是否被修改
// ... 返回元素
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
public void remove() {
// 通过 Iterator.remove() 删除会同步更新 expectedModCount
ArrayList.this.remove(lastRet);
expectedModCount = modCount; // 保持同步
}
}
fail-safe(安全失败)
java.util.concurrent 包下的集合(CopyOnWriteArrayList、ConcurrentHashMap 等)使用 fail-safe 机制。迭代时操作的是数据的快照或副本,不会抛 ConcurrentModificationException:
FailSafeDemo.java
// CopyOnWriteArrayList:迭代器基于快照
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(
Arrays.asList("a", "b", "c")
);
for (String item : list) {
list.add("d"); // 不会抛异常,但迭代器看不到新增的 "d"
System.out.println(item);
}
// ConcurrentHashMap:弱一致性迭代器
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1);
map.put("b", 2);
for (Map.Entry<String, Integer> entry : map.entrySet()) {
map.put("c", 3); // 不会抛异常
}
fail-fast vs fail-safe 对比
| 维度 | fail-fast | fail-safe |
|---|---|---|
| 所在包 | java.util | java.util.concurrent |
| 代表 | ArrayList, HashMap | CopyOnWriteArrayList, ConcurrentHashMap |
| 并发修改 | 抛 ConcurrentModificationException | 正常运行 |
| 数据一致性 | 检测到不一致立即报错 | 弱一致性(可能读到旧数据) |
| 迭代器类型 | 基于原始集合 | 基于快照或弱一致性视图 |
| 内存开销 | 无额外开销 | 可能有快照副本开销 |
常见面试问题
Q1: 如何安全地在遍历中删除集合元素?
答案:
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
// 方式一:Iterator.remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if ("b".equals(it.next())) it.remove();
}
// 方式二:removeIf(JDK 8+,推荐)
list.removeIf(item -> "b".equals(item));
// 方式三:使用并发集合
CopyOnWriteArrayList<String> safeList = new CopyOnWriteArrayList<>(list);
for (String item : safeList) {
if ("b".equals(item)) safeList.remove(item);
}
Q2: ConcurrentModificationException 一定是多线程导致的吗?
答案:
不一定。单线程中,在增强 for 循环内直接调用集合的 add/remove 方法,也会触发 ConcurrentModificationException。这个异常名字有误导性,本质是"迭代期间检测到结构性修改"。
Q3: Iterable 和 Iterator 的区别?
答案:
Iterable:表示对象可以被迭代(可以用 for-each),只有一个方法iterator()返回IteratorIterator:迭代器本身,有hasNext()、next()、remove()方法
// 实现 Iterable 即可使用 for-each
public class Range implements Iterable<Integer> {
private final int start, end;
public Range(int start, int end) { this.start = start; this.end = end; }
@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
private int current = start;
public boolean hasNext() { return current < end; }
public Integer next() { return current++; }
};
}
}
for (int i : new Range(1, 5)) {
System.out.println(i); // 1, 2, 3, 4
}