
本文详解 `concurrentmodificationexception` 在遍历 `hashmap` 时调用 `put()` 导致崩溃的根本原因,并提供简洁、线程安全(单线程场景下)且高效的解决方案——使用 `putifabsent()` 或预收集键集,避免迭代器失效。
你遇到的 ConcurrentModificationException 并非源于多线程并发,而是在单线程中违反了“结构修改禁止遍历”这一核心契约。Java 的 HashMap(及其迭代器)采用快速失败(fail-fast)机制:当通过 for-each 循环(本质是调用 keySet().iterator())遍历时,若底层 HashMap 被结构性修改(如 put()、remove()),但迭代器未感知该变更,就会立即抛出此异常。
在你的原始代码中:
for (String key : letters.keySet()) { // ← 获取 keySet 迭代器
if (!letters.containsKey(String.valueOf(input.charAt(i)))) {
letters.put(...); // ← 结构性修改!破坏迭代器预期状态
}
}问题在于:外层 for (int i = ...) 每次循环都会启动一次新的 keySet() 遍历;而内层 put() 操作会改变 HashMap 的内部结构(如扩容、链表转红黑树、modCount 增加),导致当前正在使用的迭代器检测到 modCount 不匹配,从而触发异常——即使你并未显式调用 remove(),put() 同样属于结构性修改。
✅ 正确解法:避免在遍历过程中修改集合本身。有以下两种推荐方式:
方案一:使用 putIfAbsent()(推荐 ✅)
这是最简洁、语义最清晰的修复方式,完全规避遍历与修改的冲突:
Mapletters = new HashMap<>(); // 初始化首字符(可选,实际可省略) letters.put(String.valueOf(input.charAt(0)), numberOfLettersInWord(input, input.charAt(0))); // 直接遍历字符串每个字符,按需插入(无条件插入或仅当不存在时插入) for (int i = 0; i < input.length(); i++) { char c = input.charAt(i); String key = String.valueOf(c); letters.putIfAbsent(key, numberOfLettersInWord(input, c)); } System.out.println(letters); // 示例输出: {a=4, b=4, c=3}
putIfAbsent(key, value) 是原子操作:仅当 key 不存在时才执行 put,且不依赖当前 keySet() 迭代状态,因此完全绕过 ConcurrentModificationException 风险。
? 提示:你甚至可以删除初始 put(),因为 putIfAbsent 对首个字符也安全有效,代码更统一。
方案二:先收集待插入键,遍历结束后批量插入
若需更复杂的插入逻辑(如依赖已有键值计算新值),可先缓存待添加项:
SetpendingChars = new LinkedHashSet<>(); for (int i = 0; i < input.length(); i++) { pendingChars.add(input.charAt(i)); // 自动去重 } for (char c : pendingChars) { letters.put(String.valueOf(c), numberOfLettersInWord(input, c)); }
⚠️ 注意事项
- ❌ 不要使用 for-each + put()/remove() 组合,这是 ConcurrentModificationException 的典型诱因;
- ✅ putIfAbsent()、computeIfAbsent()、merge() 等 JDK 8+ 方法专为安全更新设计,优先选用;
- ? numberOfLettersInWord() 若存在性能瓶颈(重复统计同一字符),建议改用一次遍历统计算法(如 Map
累加),时间复杂度从 O(n²) 降至 O(n); - ? 本例为单线程场景;如涉及多线程读写,请改用 ConcurrentHashMap 并注意其 putIfAbsent() 等方法的线程安全性。
总结:ConcurrentModificationException 是 Java 集合框架的保护机制,提醒你“边看边改”不可取。坚持「只读遍历」或选用「原子更新方法」,即可写出健壮、可维护的集合操作代码。










