Почему удаление 1-й записи из ConcurrentHashMap не сразу отражается в итераторе, а удаление 2-й или последующих записей? - PullRequest
3 голосов
/ 26 апреля 2020

Я создал итератор (), а затем удалил 1-ую запись с карты перед итерацией. Я всегда получаю 1-й предмет, возвращаемый итератором. Но когда я удаляю 2-ю или последующие записи, текущий итератор не возвращает эту запись.

Пример удаления 1-й записи с карты:

    Map<Integer,Integer> m1 = new ConcurrentHashMap<>();
    m1.put(4, 1);
    m1.put(5, 2);
    m1.put(6, 3);
    Iterator i1 = m1.entrySet().iterator();
    m1.remove(4);        // remove entry from map
    while (i1.hasNext())
        System.out.println("value :: "+i1.next()); //still shows entry 4=1

и вывод:

value :: 4=1
value :: 5=2
value :: 6=3

Пример удаления 3-й записи с карты:

    Map<Integer,Integer> m1 = new ConcurrentHashMap<>();
    m1.put(4, 1);
    m1.put(5, 2);
    m1.put(6, 3);
    Iterator i1 = m1.entrySet().iterator();
    m1.remove(6);        // remove entry from map
    while (i1.hasNext())
        System.out.println("value :: "+i1.next()); //does not show entry 6=3

и вывод:

value :: 4=1
value :: 5=2

Почему удаление 1-й записи с карты не отражено в итераторе, но удаление 2-й или последующей записи:?

Документация Java гласит:

Итераторы, Сплитераторы и Перечисления возвращать элементы, отражающие состояние таблицы ha sh в некоторый момент времени или после создания итератора / перечисления. Они не генерируют ConcurrentModificationException.

Это означает, что его итераторы отражают состояние таблицы ha sh в момент создания итератора. И когда мы добавим или удалим запись на или с карты, итератор покажет исходные записи?

Ответы [ 4 ]

1 голос
/ 26 апреля 2020

Чтобы достичь точно однократного поведения итерации, когда вы удаляете элемент через объект Iterator, необходимо обновить структуру данных итератора, чтобы она соответствовала тому, что произошло с коллекцией. Это невозможно в текущих реализациях, потому что они не сохраняют ссылки на выдающиеся итераторы. И если бы они это сделали, им нужно было бы использовать ссылочные объекты или риск утечки памяти.

Итератор гарантированно отображает состояние карты на момент ее создания. Дальнейшие изменения могут быть отражены в итераторе, но это не обязательно.

Думайте об итераторе как о LinkedList, и у вас есть ссылка на голову. Если вам случится удалить заголовок из связанного списка и не сбросить значение head.next и начать итерацию с заголовка, вы все равно будете проходить через тот же заголовок, потому что используете устаревшую ссылку на заголовок. Но когда вы удаляете не головные элементы, предыдущий element.next обновляется.

1 голос
/ 26 апреля 2020

Проверка фактического значения выполняется в методах next(),

public final Map.Entry<K,V> next() {
    Node<K,V> p;
    if ((p = next) == null)
        throw new NoSuchElementException();
    K k = p.key;
    V v = p.val;
    lastReturned = p;
    advance();
    return new MapEntry<K,V>(k, v, map);
}

advance, если возможно, с возвратом следующего действительного узла или нулевым значением, если его нет.

Так что для первой записи K k = 4; V v = 1;, даже если она удалена. Но для последующего k,v будет решено с обновлением от advance()

Так что если вы позвоните next() после remove, его там не будет (что очевидно),

Map<Integer,Integer> m1 = new ConcurrentHashMap<>();
m1.put(4, 1);
m1.put(5, 2);
m1.put(6, 3);
Iterator<Map.Entry<Integer, Integer>> i1 = m1.entrySet().iterator();
m1.remove(4);        // remove entry from map
i1.next();  

while (i1.hasNext())
    System.out.println("value :: "+i1.next());
1 голос
/ 26 апреля 2020

Согласно эти итераторы и сплитераторы слабо согласованы. Определение «слабосогласованного» можно найти здесь :

Большинство одновременных реализаций Collection (включая большинство очередей) также отличаются от обычных java .util соглашений тем, что их Итераторы и сплитераторы обеспечивают слабосогласованный, а не быстрый обход ошибок:

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

Это означает, что любые изменения, сделанные после итератора, могут быть отражены, но это не гарантируется. Это просто нормальное поведение параллельного итератора \ сплитератора.

0 голосов
/ 26 апреля 2020

Ответ содержится в документации, которую вы цитировали:

Итераторы, Сплитераторы и Перечисления возвращают элементы, отражающие состояние таблицы ha sh в некоторой точке в или после создание итератора / перечисления.

При ИЛИ с. Итератор может отображать или не отображать изменения на карте с момента его создания.

Разработчикам не практично применять более точное поведение во время одновременной модификации и итерации, но оно не нарушено.

...