hashmap遍历时用map.remove方法为什么会报错?

本文解析了在遍历HashMap时删除元素导致的ConcurrentModificationException异常原因,并提供了解决方案。

笔者最近在调试项目bug的时候,遇到了一个很奇怪的bug,就是在对hashmap集合进行遍历的时候,同时做了remove操作,这个操作最后导致抛出了java.util.ConcurrentModificationException的错误。
带着疑惑,下面参考着源码,分析问题的原因。
首先,重现问题,构造一个map并往里面加元素:

private static HashMap<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);  
	        }  
	}

然后移除一些元素,此时就会报java.util.ConcurrentModificationException错误

    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");  
         }  

报错

从报错中可以看出,HashMap$HashIterator.nextNode这个方法有代码错误了,点进去看,大概知道HashMap.this.modCount != this.expectedModCount 成立

再看一下hashmap的remove操作是做了什么:

这里对modCount进行了自增操作,表示操作动作+1。再看modCount和expectedModCount是什么东西


可以看出迭代器初始化的时候就对modCount和expectedModCount进行同步。
到此,可以看出报错的原因:

  • hashmap里维护了一个modCount变量,迭代器里维护了一个expectedModCount变量,一开始两者是一样的。
  • 每次进行hashmap.remove操作的时候就会对modCount+1,此时迭代器里的expectedModCount还是之前的值。
  • 在下一次对迭代器进行next()调用时,判断是否HashMap.this.modCount != this.expectedModCount,如果是则抛出异常。

那什么情况下在遍历的时候可以删除map里面的元素呢?看下迭代器提供的remove方法:

可以看出迭代器里remove了一个元素之后会对expectedModCount重新赋值,这样再次遍历的时候就不会报错了。所以之前的代码可以改成如下写法,直接调用迭代器的remove方法。

 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");

          }
      }

总结:

  • 基本上java集合类(包括list和map)在遍历时没用迭代器进行删除了都会报ConcurrentModificationException错误,这是一种fast-fail的机制,初衷是为了检测bug。
  • 通俗一点来说,这种机制就是为了防止高并发的情况下,多个线程同时修改map或者list的元素导致的数据不一致,这是只要判断当前modCount != expectedModCount即可以知道有其他线程修改了集合。

替换机制:

  • 用迭代器的remove方法。
  • 用currentHashMap替代HashMap
### 抛出 ```java.lang.IllegalStateException``` 异常的原因 在调用 `map.remove()` 方法时抛出 `IllegalStateException` 异常,通常是因为在不合法的状态下调用了该方法,常见于使用迭代器(`Iterator`)历 `Map` 时。当使用迭代器的 `remove()` 方法之后,再次调用迭代器的 `remove()` 方法就会抛出该异常。这是因为迭代器的 `remove()` 方法只能在每次调用 `next()` 方法之后调用一次,如果违反这个规则,就会导致状态非法,从而抛出 `IllegalStateException` 异常。 以下是一个示例代码,展示了这种情况: ```java import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class MapRemoveExceptionExample { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); map.put("A", 1); map.put("B", 2); map.put("C", 3); Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, Integer> entry = iterator.next(); iterator.remove(); // 再次调用 remove() 会抛出 IllegalStateException // iterator.remove(); } } } ``` ### 解决方案 #### 1. 确保每次 `next()` 方法调用后只调用一次 `remove()` 方法 确保在使用迭代器历 `Map` 时,每次调用 `next()` 方法之后只调用一次 `remove()` 方法。如果需要移除多个元素,可以在合适的条件下调用 `next()` 方法后再调用 `remove()` 方法。 ```java import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class MapRemoveSolution { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); map.put("A", 1); map.put("B", 2); map.put("C", 3); Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, Integer> entry = iterator.next(); if (entry.getValue() == 2) { iterator.remove(); } } System.out.println(map); } } ``` #### 2. 使用 Java 8 的 `removeIf` 方法 如果不需要使用迭代器的其他功能,可以使用 Java 8 引入的 `removeIf` 方法来移除满足特定条件的元素。 ```java import java.util.HashMap; import java.util.Map; public class MapRemoveIfSolution { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); map.put("A", 1); map.put("B", 2); map.put("C", 3); map.entrySet().removeIf(entry -> entry.getValue() == 2); System.out.println(map); } } ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值