繁体   English   中英

将新元素放入 HashMap 时发生 ConcurrentModificationException

[英]ConcurrentModificationException during putting new element into 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);

代码中的主要思想——字符串输入中有一些单词(不是空的,不是 null,没有非字母符号),需要计算每个字母在那里可以找到多少次。 计数工作正常(在numberOfLettersInWord方法中)但是当我尝试将字母和数字添加到HashMap<String, Integer>时会发生一些问题 - 它正确地添加所有字母及其数字但会弹出一些错误。 对于此代码,它将显示:

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

据我所知,当没有要添加的新字母时会发生一些事情。 你能解释为什么会发生这种情况以及如何解决这个问题吗?

它应该在显示{a=4, b=4, c=3}之后有更多的数字输出,但它以异常结束(这不是真正必要的,只是它停止工作的指示器......)

此运行中使用的词是String input = "aabbabcccba";

numberOfLettersInWord返回Integer在单词input中找到字母input.charAt(i)的次数的值(这工作正常)

代码片段中的第 2 行仅用于使HashMap包含至少一行(此时已完成 null 和空检查并且运行良好)

我看到人们在为什么抛出 ConcurrentModificationException 以及如何调试它时遇到了hashmap.remove()的问题,但我不确定这是否可以用该答案解决。 我也不确定这个答案是否适用于我的案例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++) {
   letters.putIfAbsent(String.valueOf(input.charAt(i)),numberOfLettersInWord(input,input.charAt(i)));
}

我删除了一些额外的代码并开始工作,甚至所有测试都通过了

letters.keySet() 正在返回一个由 HashMap 的密钥支持的集合,然后您可以通过调用 put() 来更改它。 所以这里的冲突是 keySet 和 map 的键之间的冲突。您需要使用迭代器,或者通过每次通过外循环复制 keySet 将键提取到单独的集合中。 老实说,这个算法听起来有点贵,虽然我还没有真正尝试过更好的方法......

为什么会出现 ConcurrentModificationException?

您会收到ConcurrentModificationException ,因为您在迭代其键集时在结构上修改 map。

文档

以下是HashMap 的文档关于该主题的说明:

由该类的所有“集合视图方法”返回的迭代器都是快速失败的:如果在创建迭代器后的任何时间对 map 进行了结构修改,除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException . 因此,面对并发修改,迭代器会快速干净地失败,而不是冒着在未来不确定的时间出现任意的、不确定的行为的风险。

它提到的那些“集合视图方法”如下:

  1. HashMap#keySet() ,它返回一个Set<K>

  2. HashMap#values() ,它返回一个Collection<V>

  3. HashMap#entrySet() ,它返回一个Set<Map.Entry<K, V>>

For-Each 循环

如果您不知道,for-each 循环在幕后使用Iterator 换句话说,是这样的:

List<String> list = List.of("one", "two", "three");
for (String element : list) {
    System.out.println(element);
}

编译为:

List<String> list = List.of("one", "two", "three");
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
    String element = iterator.next();
    System.out.println(element);
}

你的代码

你有一个 for-each 循环迭代你的 map 的键集。在这个 for-each 循环中你有一个调用put ,这是一个结构修改操作,在同一个 map 上。

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

因此,很可能会抛出ConcurrentModificationException 在你的情况下,这几乎是有保证的。


解决方案

您显然是在尝试计算字符串中每个字母的频率。 这不需要您遍历 map 的密钥集。您实际上没有在 for-each 循环内的任何地方使用key变量这一事实很好地表明了这一点。 这意味着您可以简单地摆脱 for-each 循环,您的代码应该可以正常工作。

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++) {
    if (!letters.containsKey(String.valueOf(input.charAt(i)))) {
        letters.put(String.valueOf(input.charAt(i)), numberOfLettersInWord(input,input.charAt(i)));
    }
}

请注意,如果 map 尚未包含密钥,则对put的调用可以替换为对computeIfAbsent的调用。 如果该键尚未包含在 map 中(或者如果该键当前映射到null ),该方法采用键和Function计算值。 它看起来像这样:

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++) {
    letters.computeIfAbsent(String.valueOf(input.charAt(i)), key -> numberOfLettersInWord(input, key));
}

注意:上述computeIfAbsent调用的第二个参数是通过Function 表达式实现的 Function。

潜在的改进

您可以对代码进行一些改进。

将按键类型更改为字符

如果您正在计算字符的频率,那么在代码中使用Map<Character, Integer>而不是Map<String, Integer>来表示它是有意义的。

算作你 Go

我只能假设numberOfLettersInWord遍历输入字符串并计算给定字符在所述字符串中出现的次数。 这意味着您为字符串中的每个字符遍历字符串,从而导致算法效率低下。 尽管您确实进行了优化,如果您还没有为该角色计算过该角色的频率,那么您只计算该角色的频率,这样可以稍微改善一下。

但是,您已经遍历了输入字符串中的所有字符。 您不妨像 go 一样计算每个字符的频率。它可能看起来像:

String input = ...;

Map<Character, Integer> frequencies = new HashMap<>();
for (int i = 0; i < input.length(); i++) {
    Character key = input.charAt(i);

    Integer value = frequencies.get(key);
    if (value == null) {
        frequencies.put(key, 1);
    } else {
        frequencies.put(key, value + 1);
    }
}

使用compute来计数

for循环的主体可以替换为对compute的调用:

String input = ...;

Map<Character, Integer> frequencies = new HashMap<>();
for (int i = 0; i < input.length(); i++) {
    frequencies.compute(input.charAt(i), (key, value) -> {
        if (value == null) {
            return 1;
        } else {
            return value + 1;
        }
    });
}

而 lambda 表达式(实现BiFunction )可以进一步“简化”:

(key, value) -> value == null ? 1 : value + 1

使用merge计数

另一种选择是使用merge

frequencies.merge(input.charAt(i), 1, Integer::sum);

注意: Integer::sum是实现BiFunction方法参考

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2023 STACKOOM.COM