遍历HashMap的方法有多种,比如通过获取map的keySet, entrySet, iterator之后,都可以实现遍历,然而如果在遍历过程中对map进行读取之外的操作则需要注意使用的遍历方式和操作方法。

public class MapIteratorTest {private static Map<Integer, String> map = new HashMap<Integer, String>();public static void main(String[] args) {//initfor(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.ConcurrentModificationExceptionat 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方法实现

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方法就会抛异常。

//会抛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)相关推荐

  1. Java 遍历HashMap详解

    Java 遍历HashMap详解 遍历KeySet() //遍历hashmap的keySetHashMap<String, Object> mapForKey = new HashMap& ...

  2. java 遍历hashmap key_Java5种遍历HashMap数据的写法

    本文介绍了最好的Java5种遍历HashMap数据的写法,分享给大家,也给自己留一个笔记,具体如下: 通过EntrySet的迭代器遍历 Iterator < Entry < Integer ...

  3. java遍历hashMap、hashSet、Hashtable

    一.遍历HashMap Map<Integer, String> map = new HashMap<Integer, String>();   for(int i=0;i&l ...

  4. Java 遍历 HashMap

    如何遍历 hashMapA? 这里的知识点包括: HashMap.Entry<A, B> entryB 可以构建迭代器元素 entryB. HashMap.entrySet() 方法可以提 ...

  5. java五种遍历HashMap的方法和性能分析

    在本文中,我们将通过示例讨论在 Java 上遍历 HashMap 的五种最佳方法. 使用Iterator迭代 使用 For-each + entrySet 循环遍历 HashMap 使用 For-ea ...

  6. java for遍历hashmap_Java 使用for和while循环遍历HashMap的方法及示例代码

    1、使用entrySet()遍历 1) 使用while实现public static void printMap(Map mp) { Iterator it = mp.entrySet().itera ...

  7. Java - 关于HashMap通过keySet遍历kv的二次调用问题

    Java - 关于HashMap通过keySet遍历kv的二次调用问题 一. HashMap的常规遍历 二. keySet的二次遍历问题 2.1 foreach和迭代器 2.2 问题分析 2.3 总结 ...

  8. java hashmap is遍历_关于内存:在Java(或Scala)中遍历HashMap的HashMap

    我创建了一个类Foo,该类具有返回Array的方法toArray(). 现在,我有一个将字符串映射到HashMaps的HashMap,后者将对象映射到Foo.那是: HashMap> 我想创建一 ...

  9. Java中HashMap遍历的两种方式

    第一种: Map map = new HashMap(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Ma ...

最新文章

  1. java单循环 比较得分_java – 为什么两个单独的循环比一个快?
  2. C++项目中的extern C {}
  3. IOS、java支持DES加密
  4. QQ使用了什么通讯协议?为什么要这样做?为什么采用 UDP 协议,而不采用 TCP 协议实现?
  5. 8/人天,小记一次 JAVA(APP后台) 项目改造 .NET 过程(后台代码已完整开源于 Github)...
  6. javascript实用技巧--数组.
  7. 图像坐标球面投影_C/C++ 图像处理(7)------图像の球面投影算法
  8. Win7 系统下配置WinCE 5.0 模拟器网络环境
  9. [机器学习] 树模型(xgboost,lightgbm)特征重要性原理总结
  10. dsf5.0获取嵌入页面的数据
  11. TemplateBinding和Binding的区别
  12. 网页404是不是服务器没开,无法打开的网页出现404错误 知道什么意思吗?
  13. 我的王姨!YYDS!富婆这套路实在是太深了...
  14. 高德LBS开放平台携手SAE 抱团背后开发者受益
  15. 阿里巴巴收购家装公司匠多多 持股20%
  16. Java中的类和对象
  17. 配置Microsoft的包存储库时报错“不支持 ‘i386’ 体系结构解决办法”
  18. LTspice基础教程-007.voltage电压源基本设置
  19. Java 基础面试之集合
  20. 优酷对上芒果,资本愿意“追光”还是“披荆斩棘”?

热门文章

  1. 夕阳西下,小卤蛋和妲己姐姐竟然手牵手讨论Spring...
  2. 计算机毕业设计 SSM+Vue宠物管理系统 宠物商城 宠物用品购物平台 宠物领养救助系统 Java Vue MySQL数据库 远程调试 代码讲解
  3. LCA 天天爱跑步
  4. layui常用审核弹窗
  5. 数据分析实战一:教育课程案例线上平台数据分析
  6. 2023最新绿色高端生活家具装饰公司网站模板源码+织梦Dedecms内核的
  7. 计算机二级Python历年真题解答(第四套)
  8. 阿里云国际站和阿里云国内站有什么区别?
  9. 一文读懂eBPF/XDP
  10. 程序员上网搜答案被HR吐槽人品差,网友:你们公司是断网开发吗?