我正在尝试在迷宫中实现Dijkstra的最短路径算法。 (http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm)
我有两个HashSets,一个用于访问,一个用于未访问的字段。 一旦所有邻居通过算法访问了一个字段, 我想把它放在访问过的地图中,并从未访问的地图中删除它。
但是,当我尝试运行算法时,我在Netbeans中得到了一个ConcurrentModificationException。
有趣的是 - 我已经阅读了这个问题,根据我的理解,这个问题来自于试图操纵/删除数据集中的数据,而不是迭代它。但是,我在集合的迭代中得到错误,即:
for( Field unvisited : unvisitedFields ) {
由于我遇到了时间问题,解决这个问题就足够了,但如果我使用不好的做法,我很想知道解决这个问题的更好方法是什么。 下面的方法的完整代码,unvisitedFields被初始化为类变量,但具有与visitedFields相同的参数。它是一个类变量的原因是因为我将它填充在我的类的顶部,以及一个2D数组。
public void calculcateSPath(Field curLocation , Field target ) {
Set<Field> visitedFields = new HashSet<>();
ArrayList<Field> shortestPath = new ArrayList<>();
shortestPath.add( target );
curLocation .setDistance( 0 );
unvisitedFields.remove( curLocation );
visitedFields.add( curLocation );
// until all fields have a corresponding value to field, it continues searching.
while( unvisitedFields.isEmpty() == false ) {
// iterate through the Set containing all unvisited fields.
for( Field unvisited : unvisitedFields ) {
//iterate through the Set containing all visited fields.
for( Field posNeighbor : visitedFields ) {
// if the unvisited field has a visited field as neighbor
if( unvisited.allNeighbors().containsValue( posNeighbor )) {
// check if the wall between them is down
if(( unvisited.getNeighbor( Direction.up ).equals( posNeighbor ) && posNeighbor.drawDown() == false )
|| ( unvisited.getNeighbor( Direction.right ).equals( posNeighbor ) && unvisited.drawRight() == false )
|| ( unvisited.getNeighbor( Direction.down ).equals( posNeighbor ) && unvisited.drawDown() == false )
|| ( unvisited.getNeighbor( Direction.left ).equals( posNeighbor ) && posNeighbor.drawRight() == false )) {
visitedFields.add( posNeighbor );
// if so, check if the current distance is smaller than the previous distance.
if( unvisited.getDistance() > ( posNeighbor.getDistance()+1 ) ) {
// assign the new shorter distance and the connection point
unvisited.setDistance( posNeighbor.getDistance() + 1 );
unvisited.setVisitedNeighbors( 1 );
unvisited.setPrevious( posNeighbor );
}
// if not, add a count to the visited neighbors
} else {
unvisited.setVisitedNeighbors( 1 );
}
//if all neighbors have been visited, remove the field from the unvisited list and add to visited.
if( unvisited.getVisitedNeighbors() == unvisited.allNeighbors().size() ) {
unvisitedFields.remove( unvisited );
visitedFields.add( posNeighbor );
}
}
}
}
}
} // ends calculateSPath()
答案 0 :(得分:4)
来自API:
此类的迭代器方法返回的迭代器是快速失败的: 如果在创建迭代器后的任何时间修改了该集,则 除了通过迭代器自己的删除方法,Iterator之外的任何方式 抛出ConcurrentModificationException。因此,面对 并发修改,迭代器快速干净地失败, 而不是冒着任意的,非确定性的行为冒险 未来不确定的时间。
这意味着如果要在迭代时修改集合,则必须使用iterator.remove()方法。不要使用for for each循环,尝试这样的事情:
Collection items = ...
Iterator itr = items.iterator();
while(itr.hasNext()) {
Object o = itr.next();
boolean condition = ...
if(condition) {
itr.remove();
}
}
或者,如果您可以(并且希望)在迭代完成后进行修改,您可以执行以下操作:
Collection items = ...
Collection itemsToRemove = ...
for (Object item : items) {
boolean condition = ...
if (condition) {
itemsToRemove.add(item);
}
}
items.removeAll(itemsToRemove);
如果您的收藏品类型为List,那么您可以通过调用ListIterator获得listIterator()。 ListIterator通过添加允许双向遍历列表的方法来扩充Iterator,并允许通过添加和删除项目或替换当前项目来修改集合。
答案 1 :(得分:3)
在java中使用“for each”循环时,实际上你正在使用Iterator。请参阅Which is more efficient, a for-each loop, or an iterator?。
由于您正在修改底层集合(删除)而不使用迭代器的add或remove方法,因此您将获得ConcurrentModificationException
,因为Java集合故障快速:
如果迭代器抛出一个迭代器,则会被视为fail-fast ConcurrentModificationException下面两个之一 条件:
在多线程处理中:如果一个线程正在尝试修改a 当另一个线程迭代它时收集。
- 醇>
在单线程或多线程处理中:如果在创建之后 对于迭代器,容器随时可以通过任何方法进行修改 除了Iterator自己的删除或添加方法。
所以你必须明确地使用Iterator,在Iterator而不是集合上调用remove。
答案 2 :(得分:3)
一般提示:在许多情况下,可以通过转换模式来避免ConcurrentModificationException
for (Element element : collection)
{
if (someCondition) collection.remove(element); // Causes Exception!
}
进入像
这样的模式Set<Element> toRemove = new HashSet<Element>(); // Or a list
for (Element element : collection)
{
if (someCondition) toRemove.add(element);
}
collection.removeAll(toRemove);
与明确处理Iterator相比,这通常更方便,更容易理解。它也可能适用于您的情况(尽管,诚然,并没有完全遵循您的代码进入最高嵌套深度)