Iterator iterator коллекции Java (2)

Java

Итератор и ListIterator

все, что реализованоCollectionКласс коллекции интерфейса имеетiteratorметод, который возвращает реализацию, реализующуюIteratorОбъект интерфейса для обхода коллекции.IteratorИнтерфейс имеет три основных метода, а именноhasNext,next,removeметод.

ListIteratorунаследовано отIterator, специально для реализацииListобъекты интерфейса, кромеIteratorКроме методов интерфейса есть еще несколько методов.

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

Iteratorа такжеListIteratorРазница:

  • Iteratorможно использовать для обходаSet,List;ListIteratorдоступен только для обходаList.
  • Iteratorможет двигаться только назад;ListIteratorЕго можно пройти вперед или назад.
  • ListIteratorДостигнутоIteratorинтерфейс и добавляетadd,set,hasPrevious,previous,previousIndex,nextIndexметод.

отказоустойчивый

безотказный механизм(fail—fast) при использовании итератора для обхода объекта коллекции, если коллекция модифицируется (добавляется, удаляется, изменяется) в процессе обхода, она выдаетConcurrentModificationExceptionаномальный.

Например, следующий код выдастConcurrentModificationException:

List<String> stringList = new ArrayList<>();
stringList.add("abc");
stringList.add("def");

Iterator<String> iterator = stringList.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
    stringList.add("ghi");
}

ПроверятьArrayListИсходный код, вы можете знать, почему возникает исключение. Причина в том,ArrayListитератор внутреннего класса классаItrсуществует одинexpectedModCountПеременная. существуетAbstracListабстрактный класс имеетmodCountПеременная, коллекция изменится, если содержимое изменится во время ее обхода.modCountзначение . всякий раз, когда используется итераторnext()Прежде чем перейти к следующему элементу, он проверитmodCountЯвляется ли переменная равнойexpectedmodCount, если они равны, продолжить обход, иначе будет выброшено исключение.

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

Примечание. Обнаружено условие создания этого исключения.modCount != expectedmodCount. Если коллекция изменитсяmodCountЗначение просто установлено наexpectedmodCount, то исключение не будет выброшено. Таким образом, параллельные операции не могут выполняться в зависимости от того, выброшено ли это исключение.Это исключение рекомендуется только для обнаружения одновременных изменений.bug.

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

отказоустойчивый

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

Хотя механизм безопасного отказа, основанный на копировании содержимого, позволяет избежатьConcurrentModificationException, но итератор не имеет доступа к измененному содержимому, но по-прежнему является копией коллекции, полученной в момент начала обхода.

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

Как удалить элементы при циклическом просмотре коллекции

При обходе коллекции правильные методы удаления следующие:

нормальный для цикла

При использовании обычного цикла for, если вы перемещаетесь спереди назад:

ArrayList<String> stringList = new ArrayList<>();
stringList.add("abc");
stringList.add("def");
stringList.add("def");
stringList.add("ghi");

for (int i = 0;i < stringList.size(); i++) {
    String str = stringList.get(i);
    if ("def".equals(str)) {
        stringList.remove(str);
    }
}

Результат печати:

abc def ghi

Как видите, второй здесь пропущен."def". Причина в том, что в началеListизsizeза4, спереди назад, петля к указателю#1, найдено удовлетворяющее условиям, поэтому удалено#1Элементы. В настоящее времяListизsizeстать3,показатель#1указал на перед#2элемент (то есть#2элементы перемещены#1,#3переехал в#2).

И следующий цикл начнется с индекса#2Старт, просмотр перед удалением#3элементы , поэтому перед#2элементы (перемещены влево в#1) пропускается.

И если вы перемещаетесь сзади вперед, вы можете избежать воздействия движения элемента.

ArrayList<String> stringList = new ArrayList<>();
stringList.add("abc");
stringList.add("def");
stringList.add("def");
stringList.add("ghi");

for (int i = stringList.size() - 1;i >= 0; i--) {
    String str = stringList.get(i);
    if ("abc".equals(str)) {
        stringList.remove(str);
    }
}
// abc ghi

foreach, чтобы выйти из цикла после удаления

в настоящее время используетforeachКогда итератор проходит через коллекцию, он используется после удаления элемента.breakвне цикла, он не сработаетfail-fast.

for (String str : stringList) {
    if ("abc".equals(str)) {
        stringList.remove(str);
        break;
    }
}

использовать итератор

Используйте итератор, который идет с нимremoveМетод удаляет элемент и не генерирует исключение.

Iterator<String> iterator = stringList.iterator();
while (iterator.hasNext()) {
    String str = iterator.next();
    if ("abc".equals(str)) {
        iterator.remove();  // 这里是 iterator,而不是 stringList
    }
}  

Enumeration

EnumerationдаJDK1.0Представленный интерфейс предоставляет интерфейс для обхода коллекций, а коллекции, которые его используют, включаютVector,HashTableЖдать.EnumerationИтераторы не поддерживаютсяfail-fastмеханизм.

У него всего два метода интерфейса:hasMoreElements,nextElementОн используется для определения наличия элемента и получения элемента, но не может изменять данные.

Но следует отметить, чтоEnumerationИтераторы могут проходить толькоVector,HashTableЭто древняя коллекция, поэтому обычно ее не используют.

Несколько способов обхода Map в Java

Метод, используемый в записях цикла for-each для итерации

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

Map<Integer, Integer> map = new HashMap<>();  
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {  
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  
}  

Примечание: при повторении пустогоmapобъект,for-eachпетля будет бросатьNullPointerException, поэтому перед обходом следует проверять наличие нулевых ссылок.

Метод 2 Перебор ключей или значений в цикле for-each

Если вам нужно толькоmapКлюч или значение вkeySetилиvaluesдля реализации обхода вместо использованияentrySet.

Map<Integer, Integer> map = new HashMap<Integer, Integer>();  

//遍历 map 中的键  
for (Integer key : map.keySet()) {  
    System.out.println("Key = " + key);  
}  

//遍历 map 中的值  
for (Integer value : map.values()) {  
    System.out.println("Value = " + value);  
}  

Этот метод болееentrySetTraversal немного лучше по производительности, а код чище.

Способ 3. Используйте итератор для обхода

Map<Integer, Integer> map = new HashMap<Integer, Integer>();  

Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();  
  
while (entries.hasNext()) {  
    Map.Entry<Integer, Integer> entry = entries.next();  
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  
}  

Этот метод выглядит избыточным, но имеет свои преимущества, его можно вызывать при обходеiterator.remove()удалитьentries, два других метода не могут.

С точки зрения производительности этот метод аналогиченfor-eachПроизводительность обхода (т.е. способ второй).

Суммировать

  • Если только ключ(keys) или значение (values), затем используйте метод 2;
  • Если вам нужно удалить во время обходаentries, затем используйте третий метод;
  • Если требуются и ключ, и значение, используйте метод 1.