HashMap添加元素时抛出ConcurrentModificationException的原因与解决
问题描述
我编写了一段统计字符串中字母出现次数的代码,用HashMap存储字母与对应次数,代码如下:
Map<String, Integer> letters = new HashMap<String, Integer>(); letters.put(String.valueOf(input.charAt(0)), numberOfLettersInWord(input,input.charAt(0))); for (int i = 0; i < input.length(); i++) { for (String key : letters.keySet()) { if (!letters.containsKey(String.valueOf(input.charAt(i)))) { letters.put(String.valueOf(input.charAt(i)), numberOfLettersInWord(input,input.charAt(i))); } else continue; System.out.println(letters); } System.out.println(1); } System.out.println(2);
其中numberOfLettersInWord方法可正确统计单个字母的出现次数,但运行时抛出java.util.ConcurrentModificationException异常,异常栈信息如下:
1 1 {a=4, b=4} 1 1 1 1 {a=4, b=4, c=3} Exception in thread "main" java.util.ConcurrentModificationException at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1579) at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1602) at LetterCounter.count(LetterCounter.java:25) at LetterCounter.main(LetterCounter.java:11) Process finished with exit code 1
测试输入为String input = "aabbabcccba",代码能正确添加字母及次数,但在无新字母可添加时抛出异常。我了解该异常常出现在删除元素场景,但不确定添加元素时是否适用相同解决方案,希望了解异常原因及解决办法。
异常原因
ConcurrentModificationException是HashMap的**快速失败(fail-fast)**机制触发的。HashMap的迭代器(包括遍历keySet()时生成的迭代器)会维护一个expectedModCount值,与HashMap内部的modCount(记录结构修改次数,比如添加、删除元素)保持一致。当你在遍历keySet()的过程中调用put()添加新元素时,HashMap的modCount会递增,但迭代器的expectedModCount不会同步更新,下一次迭代器调用next()时发现两者不一致,就会抛出该异常。
注意:快速失败机制仅用于调试阶段发现非预期的并发修改,不保证100%能检测到,不能依赖它处理多线程场景。
解决办法
1. 优化统计逻辑(推荐)
原代码嵌套遍历keySet()属于冗余逻辑,直接遍历字符串即可完成统计,既避免异常又提升效率:
Map<String, Integer> letters = new HashMap<>(); for (int i = 0; i < input.length(); i++) { String charStr = String.valueOf(input.charAt(i)); if (!letters.containsKey(charStr)) { letters.put(charStr, numberOfLettersInWord(input, input.charAt(i))); } } System.out.println(letters);
这种方式将时间复杂度从O(n*m)(n为字符串长度,m为Map键数量)降低到O(n),彻底规避了遍历中修改Map结构的问题。
2. 使用ConcurrentHashMap替代HashMap
ConcurrentHashMap是线程安全的Map实现,它的迭代器是**弱一致(weakly consistent)**的,遍历过程中修改结构不会抛出ConcurrentModificationException,但不保证迭代器能立即看到最新修改:
Map<String, Integer> letters = new ConcurrentHashMap<>(); letters.put(String.valueOf(input.charAt(0)), numberOfLettersInWord(input,input.charAt(0))); for (int i = 0; i < input.length(); i++) { for (String key : letters.keySet()) { String charStr = String.valueOf(input.charAt(i)); if (!letters.containsKey(charStr)) { letters.put(charStr, numberOfLettersInWord(input, input.charAt(i))); } System.out.println(letters); } System.out.println(1); } System.out.println(2);
此方法保留了原代码的嵌套逻辑,但效率较低,仅建议在无法修改核心遍历逻辑时使用。
内容的提问来源于stack exchange,提问作者Ra'Uko Lord

