You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java遍历HashMap时修改元素触发ConcurrentModificationException解决方法

问题说明

使用HashMap作为计数容器存储字符串中各字符的出现次数,遍历过程需要依次执行以下操作:

  1. 将当前访问字符的计数值减1,若计数减至0则移除对应条目
  2. 调用依赖当前Map剩余计数状态的业务逻辑,要求无计数大于0的字符时characterCount.size()返回0
  3. 业务逻辑执行完成后将当前字符的计数恢复为遍历前的原值

原有实现运行时抛出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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 08:51:34