1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
 
;
 
{
//init
{
;
}
 
{
;
{
;
;
;
}
}
 
;
{
;
}
}
}

上面代码的输出结果为

1
2
3
4
5
6
7
0
deleted
ConcurrentModificationException
)
)
)
)

通过上面的输出可以发现第一个偶数key元素已经被成功remove,异常的抛出位置是在迭代器遍历下一个元素的时候。

如果把上面高亮的遍历代码替换成keySet的方式,通过keySet的remove操作同样会在遍历下个元素时抛出异常,示例如下。

1
2
3
4
5
6
7
8
;
{
{
;
;
;
}
}

 

1
2
3
4
5
6
0
deleted
ConcurrentModificationException
)
)
)

如果要实现遍历过程中进行remove操作,上面两种方式都不能使用,而是需要通过显示获取keySet或entrySet的iterator来实现。

1
2
3
4
5
6
7
8
9
10
11
;
{
;
;
{
;
    
;
 
}
}

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0
deleted
2
deleted
4
deleted
6
deleted
8
deleted
5
value1
value3
value5
value7
value9

 

分析原因

其实上面的三种遍历方式从根本上讲都是使用的迭代器,之所以出现不同的结果是由于remove操作的实现不同决定的。

首先前两种方法都在调用nextEntry方法的同一个地方抛出了异常

1
2
3
4
5
6
7
{
)
;
;
.
.
}

这里modCount是表示map中的元素被修改了几次(在移除,新加元素时此值都会自增),而expectedModCount是表示期望的修改次数,在迭代器构造的时候这两个值是相等,如果在遍历过程中这两个值出现了不同步就会抛出ConcurrentModificationException异常。

1、HashMap的remove方法实现

1
2
3
4
{
;
;
}

2、HashMap.KeySet的remove方法实现

1
2
3
{
;
}

3、HashMap.HashIterator的remove方法实现

1
2
3
4
5
6
7
8
9
10
{
)
;
)
;
;
;
;
;
}

以上三种实现方式都通过调用HashMap.removeEntryForKey方法来实现删除key的操作。在removeEntryForKey方法内只要移除了key modCount就会执行一次自增操作,此时modCount就与expectedModCount不一致了,上面三种remove实现中,只有第三种iterator的remove方法在调用完removeEntryForKey方法后同步了expectedModCount值与modCount相同,所以在遍历下个元素调用nextEntry方法时,iterator方式不会抛异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
;
;
;
;
 
{
;
;
&&
{
;
;
)
;
else
;
;
;
}
;
;
}
 
;
}

 

发散

1、如果是遍历过程中增加或修改数据呢?
增加或修改数据只能通过Map的put方法实现,在遍历过程中修改数据可以,但如果增加新key就会在下次循环时抛异常,因为在添加新key时modCount也会自增。

2、有些集合类也有同样的遍历问题,如ArrayList,通过Iterator方式可正确遍历完成remove操作,直接调用list的remove方法就会抛异常。

1
2
3
4
5
6
7
8
9
10
11
//会抛ConcurrentModificationException异常
{
;
}
 
//正确遍历移除方式
;
{
;
;
}

 

3、jdk为什么这样设计,只允许通过iterator进行remove操作?
HashMap和keySet的remove方法都可以通过传递key参数删除任意的元素,而iterator只能删除当前元素(current),一旦删除的元素是iterator对象中next所正在引用的,如果没有通过modCount、 expectedModCount的比较实现快速失败抛出异常,下次循环该元素将成为current指向,此时iterator就遍历了一个已移除的过期数据。

相关文章:

  • 2021-08-06
  • 2021-11-06
  • 2021-06-30
  • 2021-05-21
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2021-07-15
  • 2022-12-23
  • 2021-09-30
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案