Java遍历HashMap时修改元素触发ConcurrentModificationException解决方法
问题说明
使用HashMap作为计数容器存储字符串中各字符的出现次数,遍历过程需要依次执行以下操作:
- 将当前访问字符的计数值减1,若计数减至0则移除对应条目
- 调用依赖当前Map剩余计数状态的业务逻辑,要求无计数大于0的字符时
characterCount.size()返回0 - 业务逻辑执行完成后将当前字符的计数恢复为遍历前的原值
原有实现运行时抛出ConcurrentModificationException异常,错误代码如下:
HashMap<Character, Integer> characterCount = new HashMap<>(); characterCount.put('a', 2); characterCount.put('b', 1); characterCount.put('c', 1); Iterator<Map.Entry<Character, Integer>> iterator = characterCount.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<Character, Integer> entry = iterator.next(); // 从Map中给当前选中字符的计数减1 if (entry.getValue() == 1) { iterator.remove(); } else { characterCount.put(entry.getKey(), entry.getValue() - 1); } // 调用依赖剩余字符计数状态的业务逻辑 // 要求没有计数大于0的字符时characterCount.size()返回0 doSomeLogic(characterCount); // 把当前字符的计数恢复回Map characterCount.put(entry.getKey(), entry.getValue()); }
异常原因
HashMap采用fail-fast机制,使用迭代器遍历集合视图(比如entrySet()、keySet())时,仅允许通过迭代器自身的remove()方法修改集合结构。如果直接调用Map本身的put()、remove()等会改变集合结构的方法,会修改Map内部记录结构修改次数的modCount字段,迭代器下次校验时发现自己持有的expectedModCount和实际modCount不一致,就会直接抛出ConcurrentModificationException。
原有代码中两处直接调用characterCount.put()修改Map结构,是触发异常的直接原因。
正确实现方案
最稳妥的实现方式是提前把Map的所有键值对复制到独立的列表中,遍历这个独立列表而非Map的视图,这样修改原Map时就不会触发迭代器的fail-fast校验,代码如下:
HashMap<Character, Integer> characterCount = new HashMap<>(); characterCount.put('a', 2); characterCount.put('b', 1); characterCount.put('c', 1); // 提前复制所有键值对到独立列表,遍历该列表不会受原Map修改影响 List<Map.Entry<Character, Integer>> originalEntries = new ArrayList<>(characterCount.entrySet()); for (Map.Entry<Character, Integer> entry : originalEntries) { Character currentChar = entry.getKey(); Integer originalCount = entry.getValue(); // 1. 当前字符计数减1,减到0则直接移除条目 if (originalCount == 1) { characterCount.remove(currentChar); } else { characterCount.put(currentChar, originalCount - 1); } // 2. 执行业务逻辑,此时Map中仅保留计数>0的字符,size()返回值符合预期 doSomeLogic(characterCount); // 3. 恢复当前字符的原始计数 characterCount.put(currentChar, originalCount); }
这个方案的优势:
- 逻辑完全匹配需求,没有多余的兼容处理,所有字符遍历完成后Map会回到初始状态
- 完全规避fail-fast异常,不需要依赖特殊集合类的实现特性
- 性能开销极低,仅额外存储一份键值对的引用,对计数场景来说可以忽略
不要在单线程场景下为了规避这个问题选用
ConcurrentHashMap,该类是为多线程并发场景设计的,单线程使用会带来不必要的性能开销,也不符合代码语义。
内容的提问来源于stack exchange,提问作者Alê Oliveira
相关产品推荐
相关产品推荐

