簡體   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-2024 STACKOOM.COM