代码之家  ›  专栏  ›  技术社区  ›  Jimmy

如何在遍历映射和更改值时避免ConcurrentModificationException?

  •  19
  • Jimmy  · 技术社区  · 14 年前

    我有一个包含一些键(字符串)和值(pojo)的映射

    我想遍历这个映射并修改POJO中的一些数据。

    这并不好用,因为在遍历映射时不应该修改它(方法是同步的,但ConcurrentModificationException仍然出现)

    ,如果需要遍历映射并更改值,那么可以使用哪些最佳实践/方法来执行此操作?创建一个单独的地图并在我去的时候建立它,然后返回副本?

    7 回复  |  直到 14 年前
        1
  •  22
  •   T.J. Crowder    14 年前

    两种选择:

    我继承的当前代码删除了给定的条目,并在对POJO进行一些更改后将其添加回中。

    你在换 参考 去警察局?E、 那么,这个入口完全指向了别的东西?因为如果没有,根本不需要从地图上删除它,你只需要更改它。

    选择2

    如果你 需要实际更改对POJO的引用(例如,条目的值),仍然可以通过在 Map.Entry 实例来自 entrySet() . 你可以用 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());
    }
    

    探访二

    访问一个

    二=到期
    一个=uno

    …没有任何修改例外。您可能需要同步该条目,以防其他内容也在查看/破坏该条目。

        2
  •  15
  •   Stephen C    14 年前

    Map 同时添加条目将导致 ConcurrentModificationException 对大多数人来说 上课。为了 地图 没有的类(例如。 ConcurrentHashMap )不能保证迭代将访问所有条目。

    • 使用 Iterator.remove() 方法删除当前项,或
    • 使用 Map.Entry.setValue()

    对于其他类型的更改,您可能需要:

    • 地图 从当前的条目 地图 ,或
    • 构建包含要进行的更改的单独数据结构,然后应用于 地图

    最后,Google Collections和Apache Commons Collections库具有用于“转换”地图的实用程序类。

        3
  •  8
  •   Sean Patrick Floyd    6 年前

    • keySet() 允许您在键上迭代。这对您没有帮助,因为密钥通常是不可变的。
    • values() 如果您只想访问映射值,则需要。如果它们是可变对象,则可以直接更改,无需将它们放回地图中。
    • entrySet() 最强大的版本,允许您直接更改条目的值。

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

    实际上,如果只想编辑值对象,请使用“值”集合进行编辑。我想你的地图是 <String, Object> :

    for(Object o: map.values()){
        if(o instanceof MyBean){
            ((Mybean)o).doStuff();
        }
    }
    
        4
  •  3
  •   mindas    14 年前

    或者只是使用 Google collections -他们有 Maps.transformValues() Maps.transformEntries() .

        5
  •  2
  •   Neeme Praks Tim Pote    14 年前

    为了提供一个正确的答案,你应该多解释一点,你想达到什么目标。

    • 使您的POJOs线程安全 直接在POJO上进行数据更新。那么你就不需要操纵地图了。
    • 使用 ConcurrentHashMap
    • 简单哈希映射 ,但在每个修改上构建新映射,并在后台切换映射(同步切换操作或使用 AtomicReference )

    哪种方法是最好的取决于你的应用程序,很难给你任何“最佳实践”。一如既往, 用真实的数据。

        6
  •  0
  •   Buhake Sindi Tesnep    14 年前

    尝试使用 ConcurrentHashMap .

    支持full的哈希表 同时检索和 更新。

    要发生ConcurrentModificationException,通常:

    一般不允许 当另一个线程正在迭代时 它。

        7
  •  0
  •   seh Alexei    14 年前

    java.util.concurrent.atomic.AtomicReference 作为地图的值类型。在您的例子中,这意味着声明

    Map<String, AtomicReference<POJO>>
    

    你当然不需要 引用的性质,但这是一种廉价的方法,可以使值槽重新绑定而不必替换整个 Map.Entry 通过 Map#put() .

    Map.Entry#setValue() ,直到今天我才意识到这一点。