设计类网站,网站系统 建设和软件岗位职责,2024年还有新冠吗,影院网站模板文章目录 快速失败机制#xff08;fail-fast#xff09;for-each删除元素为什么报错原因分析逻辑分析 如何正确的删除元素remove 后 breakfor 循环使用 Iterator 总结 快速失败机制#xff08;fail-fast#xff09;
In systems design, a fail-fast system is one which i… 文章目录 快速失败机制fail-fastfor-each删除元素为什么报错原因分析逻辑分析 如何正确的删除元素remove 后 breakfor 循环使用 Iterator 总结 快速失败机制fail-fast
In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system’s state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them.
这是快速失败机制的英文解释。翻译过来就是系统设计中“fail-fast”指的是一种策略系统或模块被设计成在出现错误或失败时立即检测并报告。这种方法旨在通过停止正常操作而不是继续可能存在缺陷的过程来最小化失败的影响。fail-fast系统通常在操作的多个点检查系统状态以便及早发现任何失败。fail-fast模块的责任是检测错误然后让系统的更高级别处理它们。
这段话的大致意思就是fail-fast 是一种通用的系统设计思想一旦检测到可能会发生错误就立马抛出异常程序将不再往下执行
很多时候我们会把 fail-fast 归类为 Java 集合框架的一种错误检测机制但其实 fail-fast 并不是 Java 集合框架特有的机制
for-each删除元素为什么报错
下面这段代码
ListString list new ArrayList();list.add(1);list.add(2);list.add(3);for (String str : list) {if (1.equals(str)) {list.remove(str);}}System.out.println(list);在执行完之后就会报错 看一下报错的原因是在checkForComodification这里报的错。下面是具体的代码
final void checkForComodification() {if (modCount ! expectedModCount)throw new ConcurrentModificationException();}也就是说remove 的时候触发执行了 checkForComodification 方法该方法对 modCount 和 expectedModCount 进行了比较发现两者不等就抛出了 ConcurrentModificationException 异常。
原因分析
为什么会执行checkForComodification 方法呢是因为for-each的底层是迭代器Iterator配合while来实现的
ListString list new ArrayList();
list.add(1);
list.add(2);
list.add(3);
Iterator var2 list.iterator();while(var2.hasNext()) {String str (String)var2.next();if (1.equals(str)) {list.remove(str);}
}System.out.println(list);
看一下list的迭代器点进iterator这个方法发现它实现了Iterator接口 再去看一下 Itr 这个类。 private class Itr implements IteratorE {int cursor; // index of next element to returnint lastRet -1; // index of last element returned; -1 if no suchint expectedModCount modCount;// prevent creating a synthetic constructorItr() {}public boolean hasNext() {return cursor ! size;}SuppressWarnings(unchecked)public E next() {checkForComodification();int i cursor;if (i size)throw new NoSuchElementException();Object[] elementData ArrayList.this.elementData;if (i elementData.length)throw new ConcurrentModificationException();cursor i 1;return (E) elementData[lastRet i];}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();}}Overridepublic void forEachRemaining(Consumer? super E action) {Objects.requireNonNull(action);final int size ArrayList.this.size;int i cursor;if (i size) {final Object[] es elementData;if (i es.length)throw new ConcurrentModificationException();for (; i size modCount expectedModCount; i)action.accept(elementAt(es, i));// update once at end to reduce heap write trafficcursor i;lastRet i - 1;checkForComodification();}}final void checkForComodification() {if (modCount ! expectedModCount)throw new ConcurrentModificationException();}}也就是说 new Itr() 的时候 expectedModCount 被赋值为 modCount而 modCount 是 ArrayList 中的一个计数器用于记录 ArrayList 对象被修改的次数。ArrayList 的修改操作包括添加、删除、设置元素值等。每次对 ArrayList 进行修改操作时modCount 的值会自增 1。
在迭代 ArrayList 时如果迭代过程中发现 modCount 的值与迭代器的 expectedModCount 不一致则说明 ArrayList 已被修改过此时会抛出 ConcurrentModificationException 异常。这种机制可以保证迭代器在遍历 ArrayList 时不会遗漏或重复元素同时也可以在多线程环境下检测到并发修改问题。
逻辑分析
ListString list new ArrayList();list.add(1);list.add(2);list.add(3);for (String str : list) {if (1.equals(str)) {list.remove(str);}}System.out.println(list);由于 list 此前执行了 3 次 add 方法。
add 方法调用 ensureCapacityInternal 方法ensureCapacityInternal 方法调用ensureExplicitCapacity 方法ensureExplicitCapacity 方法中会执行 modCount
所以 modCount 的值在经过三次 add 后为 3于是 new Itr() 后 expectedModCount 的值也为 3回到前面去看一下 Itr 的源码。
接着来执行 for-each 的循环遍历。
执行第一次循环时发现“沉默王二”等于 str于是执行 list.remove(str)。
remove 方法调用 fastRemove 方法fastRemove 方法中会执行 modCount
modCount 的值变成了 4。
第二次遍历时会执行 Itr 的 next 方法String str (String) var3.next();next 方法就会调用 checkForComodification 方法。
此时 expectedModCount 为 3modCount 为 4就只好抛出 ConcurrentModificationException 异常了。
如何正确的删除元素
remove 后 break
ListString list new ArrayList();
list.add(1);
list.add(2);
list.add(3);for (String str : list) {if (1.equals(str)) {list.remove(str);break;}
}
break 后循环就不再遍历了意味着 Iterator 的 next 方法不再执行了也就意味着 checkForComodification 方法不再执行了所以异常也就不会抛出了。
但是呢当 List 中有重复元素要删除的时候break 就不合适了。
for 循环
ListString list new ArrayList();
list.add(1);
list.add(2);
list.add(3);
for (int i 0; i list.size(); i) {String str list.get(i);if (1.equals(str)) {list.remove(str);}
}
for 循环虽然可以避开 fail-fast 保护机制也就说 remove 元素后不再抛出异常但是呢这段程序在原则上是有问题的。为什么呢
第一次循环的时候i 为 0list.size() 为 3当执行完 remove 方法后i 为 1list.size() 却变成了 2因为 list 的大小在 remove 后发生了变化也就意味着“2”这个元素被跳过了。能明白吗
remove 之前 list.get(1) 为“2”但 remove 之后 list.get(1) 变成了“3”而 list.get(0) 变成了“2”
使用 Iterator
ListString list new ArrayList();
list.add(1);
list.add(2);
list.add(3);IteratorString itr list.iterator();while (itr.hasNext()) {String str itr.next();if (1.equals(str)) {itr.remove();}
}
为什么使用 Iterator 的 remove 方法就可以避开 fail-fast 保护机制呢看一下 remove 的源码就明白了。
public void remove() {if (lastRet 0) // 如果没有上一个返回元素的索引则抛出异常throw new IllegalStateException();checkForComodification(); // 检查 ArrayList 是否被修改过try {ArrayList.this.remove(lastRet); // 删除上一个返回元素cursor lastRet; // 更新下一个元素的索引lastRet -1; // 清空上一个返回元素的索引expectedModCount modCount; // 更新 ArrayList 的修改次数} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException(); // 抛出异常}
}
删除完会执行 expectedModCount modCount保证了 expectedModCount 与 modCount 的同步
总结
在使用 foreach 循环或称 for-each 循环遍历集合时通常不能直接删除集合中的元素原因如下
Concurrent Modification Exception: 当使用 foreach 循环遍历集合时集合的结构不能被修改例如添加或删除元素否则会导致 ConcurrentModificationException 异常。这是因为 foreach 循环在背后使用迭代器来遍历集合而迭代器在遍历时会维护一个 expected modCount修改计数器如果在遍历过程中修改了集合的结构迭代器会检测到并抛出异常。
Invalidation of Iterator: 删除元素后集合的结构发生变化这可能会使当前的迭代器失效。如果集合的结构发生了变化迭代器可能无法正确遍历集合的剩余部分或者导致未定义行为。
Potential Logical Errors: 直接在 foreach 循环内删除元素可能会导致逻辑错误。例如如果不正确地更新迭代器或集合的大小可能会导致遍历的元素不完整或错误。
为了安全地从集合中删除元素应该使用迭代器的 remove() 方法。迭代器的 remove() 方法允许在遍历时安全地删除当前元素同时更新集合的结构和迭代器的状态避免了上述问题。