Java遍历HashMap并修改(remove)
Java遍历HashMap并修改(remove)
遍历HashMap的⽅法有多种,⽐如通过获取map的keySet, entrySet, iterator之后,都可以实现遍历,然⽽如果在遍历过程中对map进⾏读取之外的操作则需要注意使⽤的遍历⽅式和操作⽅法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25public class MapIterator Test {
private static Map < Integer , String > map = new HashMap < Integer , String > ( ) ; public static void main ( String [ ] args ) {
for ( int i = 0 ; i < 10 ; i ++ ) {
map . put ( i , "value" + i ) ;
}
for ( Map . Entry < Integer , String > entry : map . entrySet ( ) ) {
Integer key = entry . getKey ( ) ;
裁判的英文if ( key % 2 == 0 ) {
System . out . println ( "To delete key " + key ) ;
map . remove ( key ) ;
System . out . println ( "The key " + + key + " was deleted" ) ;
}
}
System . out . println ( "map size = " + map . size ( ) ) ;
for ( Map . Entry < Integer , String > entry : map . entrySet ( ) ) {
System . out . println ( entry . getKey ( ) + " = " + entry . getValue ( ) ) ;
}
}
}
上⾯代码的输出结果为
钢铁侠字幕To delete key 0 The key 0 was deleted Exception in thread "main" java.util.ConcurrentModificationException at
高中英语语法练习题java.util.Entry(HashMap.java:793) at java.util.(HashMap.java:834) at
java.util.(HashMap.java:832) at llection.MapIteratorTest.main(MapIteratorTest.java:60)
1 2 3 4 5 6 7To delete key 0
The key 0 was deleted
Exception in thread "main" java . util . ConcurrentModificationException
at java . util . HashMap $ HashIterator . nextEntry ( HashMap . java : 793 )
at java . util . HashMap $ EntryIterator . next ( HashMap . java : 834 )
at java . util . HashMap $ EntryIterator . next ( HashMap . java : 832 )
at com . gpzuestc . collection . MapIteratorTest . main ( MapIteratorTest . java : 60 )
通过上⾯的输出可以发现第⼀个偶数key元素已经被成功remove,异常的抛出位置是在迭代器遍历下⼀个元素的时候。
如果把上⾯⾼亮的遍历代码替换成keySet的⽅式,通过keySet的remove操作同样会在遍历下个元素时抛出异常,⽰例如下。
Set<Integer> keySet = map.keySet(); for(Integer key : keySet){ if(key % 2 == 0){ System.out.println("To delete key " + key); ve(key); System.out.println("The key " + + key + " was deleted"); } }
1 2 3 4 5 6 7 8Set <Integer> keySet = map . keySet ( ) ;
for ( Integer key : keySet ) {
if ( key % 2 == 0 ) {
System . out . println ( "To delete key " + key ) ;
keySet . remove ( key ) ;
System . out . println ( "The key " + + key + " was deleted" ) ; }
}
To delete key 0 The key 0 was deleted Exception in thread "main" java.util.ConcurrentModificationException at
java.util.Entry(HashMap.java:793) at java.util.(HashMap.java:828) at llection.MapIteratorTest.main(MapIteratorTest.java:49)
1 2 3To delete key 0
The key 0 was deleted
Exception in thread "main" java . util . ConcurrentModificationException
4 5 6at java . util . HashMap $ HashIterator . nextEntry ( HashMap . java : 793 )
at java . util . HashMap $ KeyIterator . next ( HashMap . java : 828 )
at com . gpzuestc . collection . MapIteratorTest . main ( MapIteratorTest . java : 49 )
如果要实现遍历过程中进⾏remove操作,上⾯两种⽅式都不能使⽤,⽽是需要通过显⽰获取keySet或entrySet的iterator来实现。
1 2 3 4 5 6 7 8 9 10 11Iterator < Map . Entry < Integer , String >> it = map . entrySet ( ) . iterator ( ) ; while ( it . hasNext ( ) ) {
Map . Entry < Integer , String > entry = it . next ( ) ;
Integer key = entry . getKey ( ) ;
if ( key % 2 == 0 ) {
System . out . println ( "To delete key " + key ) ;
it . remove ( ) ;
System . out . println ( "The key " + + key + " was deleted" ) ;
}
}
To delete key 0 The key 0 was deleted To delete key 2 The key 2 was deleted To delete key 4 The key 4 was deleted To delete key 6 The key 6 was deleted To delete key 8 The key 8 was deleted map size = 5 1 = value1 3 = value3 5 = value5 7 = value7 9 = value9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16To delete key 0
The key 0 was deleted To delete key 2
The key 2 was deleted To delete key 4
The key 4 was deleted To delete key 6
The key 6 was deleted To delete key 8
The key 8 was deleted map size = 5
1 = value1
3 = value3
5 = value5
7 = value7
9 = value9
分析原因
其实上⾯的三种遍历⽅式从根本上讲都是使⽤的迭代器,之所以出现不同的结果是由于remove操作的实现不同决定的。⾸先前两种⽅法都在调⽤nextEntry⽅法的同⼀个地⽅抛出了异常
英语冠军1 2 3 4 5 6 7final Entry < K , V > nextEntry ( ) {
if ( modCount != expected ModCount )
throw new ConcurrentModificationException ( ) ; Entry < K , V > e = next ;
. . .
. . .
}厌倦的英文
这⾥modCount是表⽰map中的元素被修改了⼏次(在移除,新加元素时此值都会⾃增),⽽expectedModCount是表⽰期望的修改次数,在迭代器构造的时候这两个值是相等,如果在遍历过程中这两个值出现了不同步就会抛出ConcurrentModificationException异常。
1、HashMap的remove⽅法实现
public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); }
1 2 3 4public V remove ( Object key ) {
Entry < K , V > e = removeEntryForKey ( key ) ; return ( e == null ? null : e . value ) ;miluo
}
2、HashMap.KeySet的remove⽅法实现
public boolean remove(Object o) { return veEntryForKey(o) != null; }
1 2 3public boolean remove ( Object o ) {
return HashMap . this . removeEntryForKey ( o ) != null ; }
gianniversace
3、HashMap.HashIterator的remove⽅法实现
public void remove() { if (current == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); Object k = current.key; current = null; veEntryForKey(k); expectedModCount = modCount; }
1 2 3 4 5 6 7 8 9 10public void remove ( ) {
if ( current == null )
throw new IllegalStateException ( ) ;
if ( modCount != expectedModCount )
throw new ConcurrentModificationException ( ) ; Object k = current . key ;
current = null ;
HashMap . this . removeEntryForKey ( k ) ; expectedModCount = modCount ;
}
以上三种实现⽅式都通过调⽤veEntryForKey⽅法来实现删除key的操作。在removeEntryForKey⽅法内只要移除了key modCount就会执⾏⼀次⾃增操作,此时modCount就与expectedModCount不⼀致了,上⾯三种remove实现中,只有第三种iterator 的remove⽅法在调⽤完removeEntryForKey⽅法后同步了expectedModCount值与modCount相同,所以在遍历下个元素调⽤nextEntry⽅法时,iterator⽅式不会抛异常。
final Entry<K,V> removeEntryForKey(Object key) { int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; while (e != null) { Entry<K,V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
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 26final Entry < K , V > removeEntryForKey ( Object key ) {
int hash = ( key == null ) ? 0 : hash ( key . hashCode ( ) ) ;
int i = indexFor ( hash , table . length ) ;
Entry < K , V > prev = table [ i ] ;
Entry < K , V > e = prev ;
while ( e != null ) {
Entry < K , V > next = e . next ;
Object k ;
if ( e . hash == hash &&
( ( k = e . key ) == key || ( key != null && key . equals ( k ) ) ) ) { modCount ++ ;
size -- ;
if ( prev == e )
table [ i ] = next ;
elpuppet英语怎么读
prev . next = next ;
e . recordRemoval ( this ) ;
return e ;
}
prev = e ;
e = next ;
}
return e ;
}
发散
1、如果是遍历过程中增加或修改数据呢?
thekillers增加或修改数据只能通过Map的put⽅法实现,在遍历过程中修改数据可以,但如果增加新key就会在下次循环时抛异常,因为在添加新key 时modCount也会⾃增。
2、有些集合类也有同样的遍历问题,如ArrayList,通过Iterator⽅式可正确遍历完成remove操作,直接调⽤list的remove⽅法就会抛异常。
//会抛ConcurrentModificationException异常 for(String str : list){ ve(str); } //正确遍历移除⽅式 Iterator<String> it = list.iterator(); while(it.hasNext()){ it.next(); it.remove(); }
1 2 3 4 5 6 7 8 9 10 11//会抛ConcurrentModificationException异常for ( String str : list ) {
list . remove ( str ) ;
}
//正确遍历移除⽅式
Iterator <String> it = list . iterator ( ) ;
while ( it . hasNext ( ) ) {
it . next ( ) ;
it . remove ( ) ;
牛津国际公学常州学校}
3、jdk为什么这样设计,只允许通过iterator进⾏remove操作?
HashMap和keySet的remove⽅法都可以通过传递key参数删除任意的元素,⽽iterator只能删除当前元素(current),⼀旦删除的元素是iterator对象中next所正在引⽤的,如果没有通过modCount、 expectedModCount的⽐较实现快速失败抛出异常,下次循环该元素将成为current指向,此时iterator就遍历了⼀个已移除的过期数据。