简体   繁体   English

将新元素放入 HashMap 时发生 ConcurrentModificationException

[英]ConcurrentModificationException during putting new element into HashMap

I have some code:我有一些代码:

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

The main idea in the code - there is some word in String input(not empty, not null, with no non-letter symbols), need to count how many times each letter can be found there.代码中的主要思想——字符串输入中有一些单词(不是空的,不是 null,没有非字母符号),需要计算每个字母在那里可以找到多少次。 Counting works OK (in the numberOfLettersInWord method) but when I try to add letters and digits to HashMap<String, Integer> some problems happens - it adds all letters and their numbers correctly but some error pops up.计数工作正常(在numberOfLettersInWord方法中)但是当我尝试将字母和数字添加到HashMap<String, Integer>时会发生一些问题 - 它正确地添加所有字母及其数字但会弹出一些错误。 For this code it will show:对于此代码,它将显示:

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

From what I see there is something happens when there are no new letters to be added.据我所知,当没有要添加的新字母时会发生一些事情。 Can you explain why this happens and how to solve this?你能解释为什么会发生这种情况以及如何解决这个问题吗?

It supposed to have some more digit outputs after the {a=4, b=4, c=3} was shown but it ends with the exception (it is not really necessary, just an indicator where it stops working...)它应该在显示{a=4, b=4, c=3}之后有更多的数字输出,但它以异常结束(这不是真正必要的,只是它停止工作的指示器......)

The word used in this run was String input = "aabbabcccba";此运行中使用的词是String input = "aabbabcccba";

numberOfLettersInWord returns Integer value of how many times letter input.charAt(i) was found in word input (this works ok) numberOfLettersInWord返回Integer在单词input中找到字母input.charAt(i)的次数的值(这工作正常)

line 2 in code fragment was used just to make the HashMap contain at least one line (null and empty checks already done by this moment and work well)代码片段中的第 2 行仅用于使HashMap包含至少一行(此时已完成 null 和空检查并且运行良好)

I saw people had problems with hashmap.remove() in Why is a ConcurrentModificationException thrown and how to debug it but I am not sure this is the same-same thing that can be solved with that answer.我看到人们在为什么抛出 ConcurrentModificationException 以及如何调试它时遇到了hashmap.remove()的问题,但我不确定这是否可以用该答案解决。 Also I am not sure this answer is applicable for my case ConcurrentModificationException - HashMap我也不确定这个答案是否适用于我的案例ConcurrentModificationException - HashMap

ok, i think i solved it:好的,我想我解决了它:

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

i removed some extra code and it started work, even all tests passed我删除了一些额外的代码并开始工作,甚至所有测试都通过了

letters.keySet() is returning a set which is backed by the keys of the HashMap, which you then alter by calling put(). letters.keySet() 正在返回一个由 HashMap 的密钥支持的集合,然后您可以通过调用 put() 来更改它。 So the conflict here is between the keySet and the keys of the map. You would need to use an iterator, or extract the keys into a separate collection, by copying the keySet each time through the outer loop.所以这里的冲突是 keySet 和 map 的键之间的冲突。您需要使用迭代器,或者通过每次通过外循环复制 keySet 将键提取到单独的集合中。 Honestly, the algorithm is sounding kind of expensive, though I haven't really tried to work out a better approach...老实说,这个算法听起来有点贵,虽然我还没有真正尝试过更好的方法......

Why the ConcurrentModificationException?为什么会出现 ConcurrentModificationException?

You're getting a ConcurrentModificationException because you are structurally modifying the map while iterating its key set.您会收到ConcurrentModificationException ,因为您在迭代其键集时在结构上修改 map。

Documentation文档

Here's what the documentation of HashMap says on the subject:以下是HashMap 的文档关于该主题的说明:

The iterators returned by all of this class's "collection view methods" are fail-fast : if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove method, the iterator will throw a ConcurrentModificationException .由该类的所有“集合视图方法”返回的迭代器都是快速失败的:如果在创建迭代器后的任何时间对 map 进行了结构修改,除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException . Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.因此,面对并发修改,迭代器会快速干净地失败,而不是冒着在未来不确定的时间出现任意的、不确定的行为的风险。

Those "collection view methods" it mentions are the following:它提到的那些“集合视图方法”如下:

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

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

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

For-Each Loops For-Each 循环

If you aren't aware, a for-each loop uses an Iterator behind the scenes.如果您不知道,for-each 循环在幕后使用Iterator In other words, something like this:换句话说,是这样的:

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

Is compiled down to:编译为:

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

Your Code你的代码

You have a for-each loop iterating over the key set of your map. Inside this for-each loop you have a call to put , which is a structurally-modifying operation, on the same map.你有一个 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); }

Thus, a ConcurrentModificationException is likely to be thrown.因此,很可能会抛出ConcurrentModificationException In your case it's all but guaranteed.在你的情况下,这几乎是有保证的。


Solution解决方案

You are apparently trying to count the frequencies of each letter in a string.您显然是在尝试计算字符串中每个字母的频率。 This does not require you to loop over the key set of the map. The fact you don't actually use the key variable anywhere inside the for-each loop is a good indicator of this.这不需要您遍历 map 的密钥集。您实际上没有在 for-each 循环内的任何地方使用key变量这一事实很好地表明了这一点。 This means you can simply get rid of the for-each loop and your code should work just fine.这意味着您可以简单地摆脱 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)));
    }
}

Note that call to put if the map does not already contain the key could be replaced with a call to computeIfAbsent .请注意,如果 map 尚未包含密钥,则对put的调用可以替换为对computeIfAbsent的调用。 That method takes the key and a Function that computes the value if the key is not already contained in the map (or if the key is currently mapped to null ).如果该键尚未包含在 map 中(或者如果该键当前映射到null ),该方法采用键和Function计算值。 It would look something like this:它看起来像这样:

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

Note: The second argument the above computeIfAbsent call is a Function implemented via a lambda expression .注意:上述computeIfAbsent调用的第二个参数是通过Function 表达式实现的 Function。

Potential Improvements潜在的改进

There may be a couple of improvements you could make to your code.您可以对代码进行一些改进。

Change Key Type to Character将按键类型更改为字符

Given you're counting the frequency of characters , it would make sense to represent that in the code by using a Map<Character, Integer> instead of a Map<String, Integer> .如果您正在计算字符的频率,那么在代码中使用Map<Character, Integer>而不是Map<String, Integer>来表示它是有意义的。

Count as You Go算作你 Go

I can only assume that numberOfLettersInWord loops over the input string and counts how many times the given character occurs in said string.我只能假设numberOfLettersInWord遍历输入字符串并计算给定字符在所述字符串中出现的次数。 This means you loop over the string for each character in the string, resulting in an inefficient algorithm.这意味着您为字符串中的每个字符遍历字符串,从而导致算法效率低下。 Though you do have optimization where you only compute the frequency of a character if you haven't already done so for that character, so that improves things a little.尽管您确实进行了优化,如果您还没有为该角色计算过该角色的频率,那么您只计算该角色的频率,这样可以稍微改善一下。

However, you're already looping over all the characters in the input string.但是,您已经遍历了输入字符串中的所有字符。 You might as well count the frequency of each character as you go. It could look something like:您不妨像 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);
    }
}

Use compute to Count使用compute来计数

The body of that for loop can be replaced with a call to 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;
        }
    });
}

And that lambda expression (implementing a BiFunction ) can be "simplified" even more:而 lambda 表达式(实现BiFunction )可以进一步“简化”:

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

Use merge to Count使用merge计数

Another option is to use merge :另一种选择是使用merge

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

Note: The Integer::sum is a method reference implementing a BiFunction .注意: Integer::sum是实现BiFunction方法参考

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

相关问题 在 0 索引处放入 HashMap 导致 ConcurrentModificationException - Putting into HashMap at 0 index is causing ConcurrentModificationException HashMap中的ConcurrentModificationException - ConcurrentModificationException in HashMap 使用 Map 接口将元素放入 HashMap - Putting element into HashMap with Map interface 在将嵌套哈希图写入文件并在Java中删除元素时发生ConcurrentModificationException错误 - ConcurrentModificationException error while writing nested hashmap to a file and removing element in Java 可以在没有迭代的情况下在 HashMap 中获取、放置和删除元素导致 ConcurrentModificationException? - Can get, put & remove element in HashMap without iteration cause ConcurrentModificationException? 从HashMap中删除元素时发生异常java.util.ConcurrentModificationException - Exception when removing element from HashMap java.util.ConcurrentModificationException Java HashMap中的ConcurrentModificationException程序 - ConcurrentModificationException Program in java HashMap 在HashMap中澄清@ConcurrentModificationException - Clarification @ConcurrentModificationException in HashMap java中的并发修改异常哈希图 - concurrentmodificationexception hashmap in java 为什么在 HashMap 中出现 ConcurrentModificationException - Why getting ConcurrentModificationException in HashMap
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM