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     }

使用迭代器iteratorremove方法实现修改集合操作。

在循环体内先print graph,在循环外print graph 写过如下:循环体内先后删除含b节点的边,循环体外无边。

b -> c : 2
a -> b : 3


a -> b : 3


循环外

 

不过目前尚不清楚为何源代码没有thorw exception ConcurrentModificationException,希望有了解的朋友可以提示下!

 

posted @ 2018-03-22 14:39  丿隔岸观灬  阅读(239)  评论(0编辑  收藏  举报