Как избежать ConcurrentModificationException при итерации по карте и изменении значений? - PullRequest
19 голосов
/ 02 ноября 2010

У меня есть карта, содержащая некоторые ключи (строки) и значения (POJO)

Я хочу перебрать эту карту и изменить некоторые данные в POJO.

текущий код, который я унаследовал, удаляет данную запись и добавляет ее обратно после внесения некоторых изменений в POJO.

Это не работает должным образом, так как вы не должны изменять карту во время итерации.это (метод синхронизирован, но ConcurrentModificationException все еще появляется)

У меня вопрос , если мне нужно перебрать карту и изменить значения, каковы лучшие практики / методы, которые я могу использовать дляделать это?Чтобы создать отдельную карту и создать ее на ходу, а затем вернуть копию?

Ответы [ 7 ]

21 голосов
/ 02 ноября 2010

Два варианта:

Параметр 1

Текущий унаследованный код удаляет данную запись и добавляет ее обратно после внесения некоторых изменений.в POJO.

Вы меняете ссылку на POJO?Например, поэтому вход указывает на что-то еще целиком?Потому что, если нет, вам вообще не нужно удалять его с карты, вы можете просто изменить его.

Вариант 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());
}

Вывод (в произвольном порядке):

Посещение двух
Изменение его
Посещение одного
Посещение трех
два = DUE
one = uno
three = tre

... без исключения модификации.Возможно, вы захотите синхронизировать это в случае, если что-то еще просматривает / удаляет эту запись.

15 голосов
/ 02 ноября 2010

Итерация по Map и одновременное добавление записей приведут к ConcurrentModificationException для большинства Map классов. А для классов Map, которые этого не делают (например, ConcurrentHashMap), нет гарантии, что итерация посетит все записи.

В зависимости от того, что именно вы делаете, вы можете выполнять следующие действия во время итерации:

  • используйте метод Iterator.remove() для удаления текущей записи или
  • используйте метод Map.Entry.setValue() для изменения значения текущей записи.

Для других типов изменений вам может потребоваться:

  • создайте новый Map из записей в текущем Map или
  • создать отдельную структуру данных, содержащую изменения, которые необходимо внести, а затем применить к Map.

И, наконец, в библиотеках Google Collections и Apache Commons Collections есть служебные классы для «преобразования» карт.

8 голосов
/ 02 ноября 2010

Для таких целей вы должны использовать представления коллекций, которые предоставляет карта:

  • 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();
    }
}
3 голосов
/ 02 ноября 2010

Создать новую карту (mapNew).Затем выполните итерацию по существующей карте (mapOld) и добавьте все измененные и преобразованные записи в mapNew.После завершения итерации поместите все значения из mapNew в mapOld.Это может быть недостаточно, если объем данных велик.

Или просто используйте коллекции Google - у них есть Maps.transformValues() и Maps.transformEntries().

2 голосов
/ 02 ноября 2010

Чтобы дать правильный ответ, вы должны объяснить немного больше, чего вы пытаетесь достичь.

Тем не менее, некоторые (возможно, полезные) советы:

  • сделать ваши POJO поточно-ориентированными и напрямую обновлять данные на POJO.Тогда вам не нужно манипулировать картой.
  • использовать ConcurrentHashMap
  • продолжать использовать простой HashMap , но строитьновую карту в каждой модификации и переключение карт за кулисами (синхронизация операции переключения или использование AtomicReference )

Какой подход лучше всего зависит в значительной степени от вашего приложения, труднодать вам любую "лучшую практику".Как всегда, создайте свой собственный тест с реалистичными данными.

0 голосов
/ 02 ноября 2010

Другой подход, несколько замученный, заключается в использовании java.util.concurrent.atomic.AtomicReference в качестве типа значения вашей карты.В вашем случае это будет означать объявление вашей карты типа

Map<String, AtomicReference<POJO>>

Вам, безусловно, не нужен характер ссылки atomic , но это дешевый способ сделать слоты значенийможно перезаписать без необходимости замены всего Map.Entry на Map#put().

Тем не менее, прочитав некоторые другие ответы здесь, я тоже рекомендую использовать Map.Entry#setValue(), который у меня былникогда не нуждался и не заметил до сегодняшнего дня.

0 голосов
/ 02 ноября 2010

Попробуйте использовать ConcurrentHashMap .

Из JavaDoc,

Хеш-таблица, поддерживающая полное параллелизм поиска и регулируемый ожидаемый параллелизм для обновления.

Для исключения ConcurrentModificationException, как правило:

это вообще не допустимо для один поток, чтобы изменить коллекцию в то время как другой поток перебирает это.

...