for-each & iterator 遍历修改容器出错, Mutation undermines an iterator
问题引入:
在一次实验作业中,出现了这样的bug:
对于有向加权图graph的边集合(已保存 a->b , b->c 两条边),通过for-each循环删除 含有节点b的边:a->b(b作为target),b->c(b作为source),根据逻辑,循环结束,graph为空。
代码如下:
1 public boolean remove(String vertex) { 2 for (Edge edge : edges) { 3 System.out.println(toString()); 4 System.out.println(); 5 if (edge.containSource(vertex)) { // as source 6 edges.remove(edge); 7 } 8 if (edge.containTarget(vertex)) { // as target 9 edges.remove(edge); 10 } 11 } 12 13 System.out.println("循环外"+toString()); // 显示循环后的graph 14 System.out.println(); 15 vertices.remove(vertex); 16 // checkRep(); 17 return true; 18 }
运行结果如下:
b -> c : 2 a -> b : 3 循环外a -> b : 3
graph中有两个edge,应当进行两次循环,但实际只输出了第一次进入循环的状态。而循环结束后graph为非空,仍含有一条含b节点的边。
问题分析:
用一个段简单的代码模拟上述过程:(遍历set,删除元素“b”,print其他元素)
1 Set<String> set = new HashSet<>(); 2 set.add("a"); 3 set.add("b"); 4 set.add("c"); 5 for (String string : set) { 6 if (string.equals("b")) { 7 set.remove(string); 8 }else { 9 System.out.println(string); 10 } 11 }
运行结果:
a Exception in thread "main" java.util.ConcurrentModificationException at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1494) at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1517) at test.main(test.java:27)
exception!当多个线程对Collection进行操作时,若其中某一个线程遍历集合时,该集合的内容被其他线程所改变,则会抛出ConcurrentModificationException异常。
分析:为保证在遍历集合时不出错误,就应该保证便利过程中不对集合产生结构上的修改。
经验证,原问题也系ConcurrentModification,但没有thorw exception ,原因尚不明确,以后讨论。
问题解决:
Java迭代器:
迭代器模式:就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细节。
概述
Java集合框架的集合类,我们有时候称之为容器。容器的种类有很多种,比如ArrayList、LinkedList、HashSet...,每种容器都有自己的特点,ArrayList底层维护的是一个数组;LinkedList是链表结构的;HashSet依赖的是哈希表,每种容器都有自己特有的数据结构。
因为容器的内部结构不同,很多时候可能不知道该怎样去遍历一个容器中的元素。所以为了使对容器内元素的操作更为简单,Java引入了迭代器模式!
把访问逻辑从不同类型的集合类中抽取出来,从而避免向外部暴露集合的内部结构。
迭代器的特性使得迭代器可以在迭代期间从集合中移除元素。
问题引入中的删除节点函数修改后如下:
1 public boolean remove(String vertex) { 2 Iterator<Edge> iter = edges.iterator(); 3 while (iter.hasNext()) { 4 Edge edge = (Edge) iter.next(); 5 if (edge.containSource(vertex)) { // as source 6 iter.remove(); 7 } 8 else if (edge.containTarget(vertex)) { // as target 9 iter.remove(); 10 } 11 } 12 13 checkRep(); 14 return true; 15 }
使用迭代器iterator的remove方法实现修改集合操作。
在循环体内先print graph,在循环外print graph 写过如下:循环体内先后删除含b节点的边,循环体外无边。
b -> c : 2 a -> b : 3 a -> b : 3 循环外
不过目前尚不清楚为何源代码没有thorw exception ConcurrentModificationException,希望有了解的朋友可以提示下!