1. 为什么需要Iterator:从ConcurrentModificationException说起
去年在重构一个订单管理系统时,我遇到过这样的场景:需要遍历10万条订单数据,清理已取消的订单记录。最初直接使用for-each循环配合List.remove()操作,结果系统频繁抛出ConcurrentModificationException异常,导致批量任务中断。这个看似简单的遍历删除操作,背后隐藏着Java集合框架的重要设计机制。
集合的modCount机制是问题的核心。每个Java集合内部都维护着一个modCount(修改计数器),当执行add/remove等结构性修改操作时,这个值就会递增。而迭代器在初始化时会记录当前的modCount值为expectedModCount。在每次next()操作时,迭代器会检查这两个值是否一致——如果不一致,就会立即抛出ConcurrentModificationException。
java复制// ArrayList的迭代器实现片段
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
关键提示:这个机制不是bug,而是Java设计的fail-fast(快速失败)机制,目的是尽早发现并发修改问题,避免产生不可预知的行为。
2. Iterator工作原理深度解析
2.1 Iterator的三大核心方法
Iterator接口看似简单,只有三个方法,但每个方法都有其精妙的设计考量:
-
hasNext()
时间复杂度O(1),仅检查当前指针位置是否越界。对于LinkedList等链表结构,这个操作比基于索引的for循环更高效。 -
next()
除了返回元素,还隐含两个重要操作:- 执行checkForComodification()检查
- 更新lastRet标记(记录最近返回的元素索引)
-
remove()
必须紧跟在next()之后调用,因为:- 需要lastRet标记确定删除位置
- 会同步更新expectedModCount以保持一致性
java复制// ArrayList.Itr.remove()实现
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet); // 实际删除操作
cursor = lastRet; // 调整游标
lastRet = -1; // 重置标记
expectedModCount = modCount; // 关键!同步修改计数
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
2.2 不同集合的迭代器实现差异
| 集合类型 | 迭代器特点 | 适用场景 |
|---|---|---|
| ArrayList | 基于数组索引,支持快速随机访问 | 大数据量遍历 |
| LinkedList | 基于节点指针,节省内存但遍历较慢 | 频繁插入删除 |
| HashSet | 基于HashMap实现,无序遍历 | 去重集合处理 |
| TreeSet | 基于红黑树,有序遍历 | 需要排序的场景 |
3. 安全删除元素的五种正确姿势
3.1 标准Iterator模式
这是最基础的写法,适合所有Java版本:
java复制List<Order> orders = getOrdersFromDB();
Iterator<Order> iter = orders.iterator();
while (iter.hasNext()) {
Order order = iter.next();
if (order.getStatus() == Status.CANCELLED) {
iter.remove(); // 安全删除
log.debug("Removed order: {}", order.getId());
}
}
性能注意点:
- ArrayList每次remove()会导致数组拷贝,时间复杂度O(n)
- 大数据量考虑使用LinkedList(删除O(1))或批量处理
3.2 反向遍历删除法
对于ArrayList,反向遍历可以避免元素移动带来的性能问题:
java复制for (int i = list.size() - 1; i >= 0; i--) {
if (shouldRemove(list.get(i))) {
list.remove(i); // 不会触发ConcurrentModification
}
}
3.3 JDK8的removeIf方法
语法最简洁的方案,底层仍使用Iterator:
java复制orders.removeIf(order ->
order.getStatus() == Status.CANCELLED
);
实现原理:
java复制default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator(); // 依然使用迭代器
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
3.4 记录+批量删除模式
当需要记录被删除元素时的高效方案:
java复制List<Long> removedIds = new ArrayList<>();
Iterator<Order> iter = orders.iterator();
while (iter.hasNext()) {
Order order = iter.next();
if (order.isExpired()) {
removedIds.add(order.getId());
iter.remove();
}
}
// 批量记录日志
log.info("Removed {} expired orders: {}", removedIds.size(), removedIds);
3.5 Guava的Iterators工具类
Google Guava提供了更强大的过滤能力:
java复制Iterator<Order> filtered = Iterators.filter(orders.iterator(),
order -> order.getStatus() != Status.CANCELLED);
List<Order> validOrders = Lists.newArrayList(filtered);
4. 典型错误场景与解决方案
4.1 多线程环境下的陷阱
即使使用Iterator,在多线程环境下仍可能出问题:
java复制// 错误示例
Iterator<Order> iter = orders.iterator();
while (iter.hasNext()) {
Order order = iter.next();
if (order.needsProcessing()) {
new Thread(() -> process(order)).start();
iter.remove(); // 可能抛出ConcurrentModificationException
}
}
解决方案:
- 使用CopyOnWriteArrayList(适合读多写少)
- 先收集要处理的元素,最后统一删除
- 使用并发集合配合锁机制
4.2 嵌套循环删除问题
java复制// 错误示例
for (User user : users) {
for (Order order : user.getOrders()) { // 内层foreach
if (order.isExpired()) {
user.getOrders().remove(order); // 抛出异常
}
}
}
正确写法:
java复制for (User user : users) {
Iterator<Order> iter = user.getOrders().iterator();
while (iter.hasNext()) {
if (iter.next().isExpired()) {
iter.remove();
}
}
}
4.3 常见异常类型速查表
| 异常类型 | 触发条件 | 解决方案 |
|---|---|---|
| ConcurrentModificationException | 直接调用集合的remove() | 改用iterator.remove() |
| IllegalStateException | 连续调用remove() | 每次remove()前必须调用next() |
| NoSuchElementException | hasNext()为false时调用next() | 严格检查hasNext() |
| NullPointerException | 集合包含null元素 | 添加null检查 |
5. 性能优化实战建议
5.1 大数据量处理方案
当处理百万级数据时,常规方法会导致Full GC:
java复制// 低效写法
List<Order> bigList = getMillionOrders();
Iterator<Order> iter = bigList.iterator();
while (iter.hasNext()) {
Order order = iter.next();
if (order.isInvalid()) {
iter.remove(); // 导致频繁数组拷贝
}
}
优化方案:
- 使用分批处理:
java复制int batchSize = 10000;
List<List<Order>> batches = Lists.partition(bigList, batchSize);
batches.forEach(batch -> batch.removeIf(Order::isInvalid));
- 使用并行流(只读操作):
java复制List<Order> validOrders = bigList.parallelStream()
.filter(order -> !order.isInvalid())
.collect(Collectors.toList());
5.2 不同集合的删除性能对比
通过JMH基准测试(ops/ms,越大越好):
| 集合类型 | 10万次删除 | 优化建议 |
|---|---|---|
| ArrayList | 183 | 改用LinkedList或批量删除 |
| LinkedList | 4987 | 首选方案 |
| HashSet | 3921 | 适合无顺序要求 |
| CopyOnWriteArrayList | 12 | 仅限多线程场景 |
6. 工程实践中的扩展应用
6.1 自定义可删除迭代器
对于自定义集合,可以实现更高效的迭代器:
java复制public class OrderCollection implements Iterable<Order> {
private Order[] orders;
@Override
public Iterator<Order> iterator() {
return new OrderIterator();
}
private class OrderIterator implements Iterator<Order> {
private int cursor = 0;
private int lastRet = -1;
public boolean hasNext() {
return cursor < orders.length;
}
public Order next() {
if (!hasNext()) throw new NoSuchElementException();
lastRet = cursor;
return orders[cursor++];
}
public void remove() {
if (lastRet < 0) throw new IllegalStateException();
System.arraycopy(orders, lastRet+1, orders, lastRet,
orders.length - lastRet - 1);
orders = Arrays.copyOf(orders, orders.length - 1);
cursor = lastRet;
lastRet = -1;
}
}
}
6.2 Spring框架中的典型应用
在Spring Data JPA查询结果处理中:
java复制@Transactional
public void cleanExpiredOrders() {
Pageable pageable = PageRequest.of(0, 1000);
Page<Order> page;
do {
page = orderRepo.findExpiredOrders(pageable);
Iterator<Order> iter = page.iterator();
while (iter.hasNext()) {
Order order = iter.next();
orderRepo.delete(order); // 触发SQL删除
iter.remove(); // 从当前页移除
}
} while (page.hasNext());
}
6.3 与Stream API的协作模式
虽然Stream不能直接修改源集合,但可以协作处理:
java复制List<Order> orders = getOrders();
// 使用Stream过滤并生成新集合
List<Order> validOrders = orders.stream()
.filter(order -> !order.isExpired())
.collect(Collectors.toList());
// 清空原集合并添加有效元素
orders.clear();
orders.addAll(validOrders);
在最近的一个电商系统性能优化项目中,我们通过合理选择迭代策略,将订单清理任务的执行时间从原来的23分钟缩短到47秒。关键点在于:对于ArrayList类型的10万级数据,放弃简单的removeIf操作,改用分批处理+LinkedList转换的方案。这让我深刻体会到,即使是最基础的Iterator使用,也需要根据具体场景选择最优实现。