[英]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...
老实说,这个算法听起来有点贵,虽然我还没有真正尝试过更好的方法......
You're getting a ConcurrentModificationException
because you are structurally modifying the map while iterating its key set.您会收到
ConcurrentModificationException
,因为您在迭代其键集时在结构上修改 map。
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 aConcurrentModificationException
.由该类的所有“集合视图方法”返回的迭代器都是快速失败的:如果在创建迭代器后的任何时间对 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:它提到的那些“集合视图方法”如下:
HashMap#keySet()
, which returns a Set<K>
. HashMap#keySet()
,它返回一个Set<K>
。
HashMap#values()
, which returns a Collection<V>
. HashMap#values()
,它返回一个Collection<V>
。
HashMap#entrySet()
, which returns a Set<Map.Entry<K, V>>
. HashMap#entrySet()
,它返回一个Set<Map.Entry<K, V>>
。
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);
}
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.在你的情况下,这几乎是有保证的。
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。
There may be a couple of improvements you could make to your code.您可以对代码进行一些改进。
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>
来表示它是有意义的。
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);
}
}
compute
to Countcompute
来计数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
merge
to Countmerge
计数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.