本文主要是介绍Java遍历HashMap并修改(remove),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
转自:https://blog.csdn.net/zmx729618/article/details/52795493遍历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 25 | public class MapIteratorTest { private static Map < Integer , String > map = new HashMap < Integer , String > ( ) ; public static void main ( String [ ] args ) { //init 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 ( ) ) ; } } } |
上面代码的输出结果为
1 2 3 4 5 6 7 | To 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操作同样会在遍历下个元素时抛出异常,示例如下。
1 2 3 4 5 6 7 8 | Set <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" ) ; } } |
1 2 3 4 5 6 | To 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 $ 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 11 | Iterator < 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" ) ; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
分析原因
其实上面的三种遍历方式从根本上讲都是使用的迭代器,之所以出现不同的结果是由于remove操作的实现不同决定的。
首先前两种方法都在调用nextEntry方法的同一个地方抛出了异常
1 2 3 4 5 6 7 | final Entry < K , V > nextEntry ( ) { if ( modCount != expectedModCount ) throw new ConcurrentModificationException ( ) ; Entry < K , V > e = next ; . . . . . . } |
这里modCount是表示map中的元素被修改了几次(在移除,新加元素时此值都会自增),而expectedModCount是表示期望的修改次数,在迭代器构造的时候这两个值是相等,如果在遍历过程中这两个值出现了不同步就会抛出ConcurrentModificationException异常。
1、HashMap的remove方法实现
1 2 3 4 | public V remove ( Object key ) { Entry < K , V > e = removeEntryForKey ( key ) ; return ( e == null ? null : e . value ) ; } |
2、HashMap.KeySet的remove方法实现
1 2 3 | public boolean remove ( Object o ) { return HashMap . this . removeEntryForKey ( o ) != null ; } |
3、HashMap.HashIterator的remove方法实现
1 2 3 4 5 6 7 8 9 10 | public 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 ; } |
以上三种实现方式都通过调用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 | 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 ; else prev . next = next ; e . recordRemoval ( this ) ; return e ; } prev = e ; e = next ; } return e ; } |
发散
1、如果是遍历过程中增加或修改数据呢?
增加或修改数据只能通过Map的put方法实现,在遍历过程中修改数据可以,但如果增加新key就会在下次循环时抛异常,因为在添加新key时modCount也会自增。
2、有些集合类也有同样的遍历问题,如ArrayList,通过Iterator方式可正确遍历完成remove操作,直接调用list的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就遍历了一个已移除的过期数据。
这篇关于Java遍历HashMap并修改(remove)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!