迭代器遍历对象 快速失败和安全失败
一、快速失败(fail—fast)
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出 Concurrent Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。每当迭代器使用 hashNext()/next() 遍历下一个元素之前,都会检测 modCount 变量是否为 expectedmodCount 值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:这里异常的抛出条件是检测到 modCount != expectedmodCount 这个条件。如果集合发生变化时修改 modCount 值刚好又设置为了 expectedmodCount 值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的 bug。
场景:java.util 包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
二、安全失败(fail—safe)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发 Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了 Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
普通的Java容器都是快速失败的比如ArrayList,concurrent 包下面的容器一般都是 安全失败的比如CopyOnWriteArrayList。
快速失败的在迭代器遍历过程中对原集合删除和添加元素都会抛出并发异常,但是可以通过迭代器的remove 接口删除单签遍历的元素,并且这个删除会立即影响元集合,因为快速失败迭代器使用的原集合。
安全失败的迭代器是复制了一份集合,迭代器产生内容就固定的,并且不能删除,删除的时候会抛出不支持的操作异常,但是这是可以通过原集合对象的集合做任意的修改。
支持快速失败的容器迭代过程演示:
package com.lomi.iterator; import com.alibaba.fastjson.JSONObject; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * 快速失败 * * @Author ZHANGYUKUN * @Date 2022/9/26 */ public class FailFast { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); list.add("6"); list.add("7"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String value = iterator.next(); System.out.println(value); if ("5".equals(value)) { //使用迭代器删除可以正常删除,使用原集合删除和添加就会抛出java.util.ConcurrentModificationException,修改不会 iterator.remove(); } //对迭代器的修改会立刻反应到原来的集合 System.out.println(JSONObject.toJSONString(list)); } } }
支持安全失败的容器迭代过程演示:
public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); list.add("6"); list.add("7"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String value = iterator.next(); System.out.println(value); if ("5".equals(value)) { //使用迭代器删除可以正常删除,使用原集合删除和添加就会抛出java.util.ConcurrentModificationException,修改不会 iterator.remove(); } System.out.println(JSONObject.toJSONString(list)); } }
一些其他情况遍历过程对集合的修改情况说明:
package com.lomi.iterator; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Spliterator; import java.util.function.Consumer; import com.alibaba.fastjson.JSONObject; /** * 快速失败 * * @Author ZHANGYUKUN * @Date 2022/9/26 */ public class FailFast2 { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); list.add("6"); list.add("7"); /* //使用增强for循环和使用迭代器情况一样,增加for内部使用的迭代器,并且只有实现了Iterator接口的集合才能使用增强for for(String value: list ){ System.out.println(value); if ("5".equals(value)) { list.remove(value); } System.out.println(JSONObject.toJSONString(list)); } */ //使用普通for循环可能有些隐藏的影响,比如重复遍历(在前面添加元素),漏遍历(前面删除元素),下标越界(保持固定的集合长度遍量)等等,主要看修改的位置 for(int i = 0;i<list.size();i++){ String value = list.get(i); System.out.println(value); if ("5".equals(value)) { list.remove("1"); } System.out.println(JSONObject.toJSONString(list)); } //果然只要实现了Iterator接口就能使用增强for循环 MyCollocation myCollocation = new MyCollocation(); for(Object o: myCollocation){ } } } class MyCollocation implements Iterable<Object> { @Override public Iterator<Object> iterator() { return null; } @Override public void forEach(Consumer<? super Object> action) { } @Override public Spliterator<Object> spliterator() { return null; } }
结论:使用iterator 增强for并且在遍历过程中有数据修改的时候,需要注意快速失败和安全失败的问题,
使用普通for,while循环并且在遍历过程中有数据修改的时候,需要主要漏遍历,重复遍历,下标越界问题。
能耍的时候就一定要耍,不能耍的时候一定要学。
--天道酬勤,贵在坚持posted on 2022-09-26 00:53 zhangyukun 阅读(127) 评论(0) 编辑 收藏 举报