簡體   English   中英

迭代地圖並更改值時如何避免ConcurrentModificationException?

[英]How to avoid ConcurrentModificationException when iterating over a map and changing values?

我有一個包含一些鍵(字符串)和值(POJO)的映射

我想迭代這個地圖並改變POJO中的一些數據。

我繼承的當前代碼刪除了給定的條目,並在對POJO進行一些更改后將其重新添加。

這不能很好地工作,因為在迭代它時你不應該修改地圖(方法是同步的,但仍然出現ConcurrentModificationException)

我的問題是 ,如果我需要遍歷地圖並更改值,我可以使用哪些最佳實踐/方法? 要創建一個單獨的地圖並按照我的方式構建,然后返回副本?

兩種選擇:

選項1

我繼承的當前代碼刪除了給定的條目,並在對POJO進行一些更改后將其重新添加。

您是否正在更改對POJO的引用 例如,入口指向其他東西完全? 因為如果沒有,根本不需要從地圖上刪除它,你可以改變它。

選項2

如果確實需要實際更改對POJO的引用(例如,條目的值),您仍然可以通過從entrySet()迭代Map.Entry實例來實現。 您可以在條目上使用setValue ,它不會修改您要迭代的內容。

例:

Map<String,String>                  map;
Map.Entry<String,String>            entry;
Iterator<Map.Entry<String,String>>  it;

// Create the map
map = new HashMap<String,String>();
map.put("one", "uno");
map.put("two", "due");
map.put("three", "tre");

// Iterate through the entries, changing one of them
it = map.entrySet().iterator();
while (it.hasNext())
{
    entry = it.next();
    System.out.println("Visiting " + entry.getKey());
    if (entry.getKey().equals("two"))
    {
        System.out.println("Modifying it");
        entry.setValue("DUE");
    }
}

// Show the result
it = map.entrySet().iterator();
while (it.hasNext())
{
    entry = it.next();
    System.out.println(entry.getKey() + "=" + entry.getValue());
}

輸出(無特定順序)是:

參觀兩個
修改它
參觀一個
參觀三
2 = DUE
一個=烏諾
3 = TRE

......沒有任何修改例外。 你可能想要同步它,以防其他東西也在查看/混淆該條目。

迭代Map並同時添加條目將導致大多數Map類的ConcurrentModificationException 對於沒有的Map類(例如ConcurrentHashMap ),無法保證迭代將訪問所有條目。

根據您正在做的事情,您可以在迭代時執行以下操作:

  • 使用Iterator.remove()方法刪除當前條目,或
  • 使用Map.Entry.setValue()方法修改當前條目的值。

對於其他類型的更改,您可能需要:

  • 創建一個新的Map中的電流從輸入Map ,或
  • 構建一個單獨的數據結構,包含要進行的更改,然后應用於Map

最后,Google Collections和Apache Commons Collections庫具有用於“轉換”地圖的實用程序類。

出於此目的,您應該使用地圖公開的集合視圖:

  • keySet()允許您遍歷鍵。 這對你沒有幫助,因為鍵通常是不可變的。
  • 如果您只想訪問地圖值,則需要values() 如果它們是可變對象,您可以直接更改,無需將它們放回到地圖中。
  • entrySet()是最強大的版本,允許您直接更改條目的值。

示例:將包含upperscore的所有鍵的值轉換為大寫

for(Map.Entry<String, String> entry:map.entrySet()){
    if(entry.getKey().contains("_"))
        entry.setValue(entry.getValue().toUpperCase());
}

實際上,如果您只想編輯值對象,請使用values集合進行編輯。 我假設您的地圖是<String, Object>類型:

for(Object o: map.values()){
    if(o instanceof MyBean){
        ((Mybean)o).doStuff();
    }
}

創建一個新地圖(mapNew)。 然后遍歷現有映射(mapOld),並將所有已更改和已轉換的條目添加到mapNew中。 迭代完成后,將mapNew中的所有值都放到mapOld中。 如果數據量很大,這可能不夠好。

或者只使用Google集合 - 它們有Maps.transformValues()Maps.transformEntries()

為了提供正確的答案,你應該多解釋一下,你想要實現的目標。

還是,一些(可能有用的)建議:

  • 使您的POJO成為線程安全的 ,並直接在POJO上進行數據更新。 然后你不需要操縱地圖。
  • 使用ConcurrentHashMap
  • 繼續使用簡單的HashMap ,但在每個修改上構建一個新的地圖並在后台切換地圖(同步切換操作或使用AtomicReference

哪種方法最好取決於您的應用程序,很難給您任何“最佳實踐”。 與往常一樣,使用真實數據制作自己的基准

嘗試使用ConcurrentHashMap

來自JavaDoc,

一個哈希表,支持檢索的完全並發性和可更新的預期並發性。

對於ConcurrentModificationException,通常會:

一個線程通常不允許修改Collection而另一個線程迭代它。

另一種有點折磨的方法是使用java.util.concurrent.atomic.AtomicReference作為地圖的值類型。 在您的情況下,這將意味着聲明您的類型地圖

Map<String, AtomicReference<POJO>>

你當然不需要引用的原子性質,但它是一種廉價的方法,使值槽可重新綁定,而不必通過Map#put()替換整個Map.Entry

盡管如此,在閱讀了其他一些回復之后,我也建議使用Map.Entry#setValue() ,這是我從未需要也沒有注意到今天。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM