Какой самый эффективный способ пройти через коллекцию?
List<Integer> a = new ArrayList<Integer>();
for (Integer integer : a) {
integer.toString();
}
или
List<Integer> a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();) {
Integer integer = (Integer) iterator.next();
integer.toString();
}
Обратите внимание, что это не является точной копией этого , этого , этого или этого , хотя один из ответов на последний вопрос близок. Причина, по которой это не обман, заключается в том, что большинство из них сравнивают циклы, когда вы вызываете их get(i)
внутри цикла, а не используете итератор.
Как предложено на Meta, я буду публиковать свой ответ на этот вопрос.
java
collections
foreach
Пол Уогланд
источник
источник
Ответы:
Если вы просто бродите по коллекции, чтобы прочитать все значения, то нет разницы между использованием итератора или нового синтаксиса цикла for, поскольку новый синтаксис просто использует итератор под водой.
Однако, если вы подразумеваете под циклом старый цикл "c-style":
Тогда новый цикл for или итератор может быть намного более эффективным, в зависимости от базовой структуры данных. Причина этого заключается в том, что для некоторых структур данных
get(i)
это операция O (n), которая делает цикл операцией O (n 2 ). Традиционный связанный список является примером такой структуры данных. Все итераторы имеют в качестве основного требования, что этоnext()
должна быть операция O (1), составляющая цикл O (n).Чтобы убедиться, что итератор используется под водой новым синтаксисом цикла for, сравните сгенерированные байт-коды из следующих двух фрагментов Java. Первый цикл for:
И второе, итератор:
Как видите, сгенерированный байт-код фактически идентичен, поэтому использование любой формы не снижает производительности. Поэтому вы должны выбрать форму цикла, которая наиболее эстетически привлекательна для вас, для большинства людей это будет цикл for-each, поскольку в нем меньше стандартного кода.
источник
for(int i; i < list.size(); i++) {
цикл стиля также должен вычислятьсяlist.size()
в конце каждой итерации, если он используется, иногда более эффективно кешировать результатlist.size()
first.Разница не в производительности, а в возможностях. При непосредственном использовании ссылки у вас есть больше возможностей явно использовать тип итератора (например, List.iterator () и List.listIterator (), хотя в большинстве случаев они возвращают одну и ту же реализацию). У вас также есть возможность ссылаться на Итератор в вашем цикле. Это позволяет вам делать такие вещи, как удаление элементов из вашей коллекции без получения исключения ConcurrentModificationException.
например
Хорошо:
Это не так, поскольку это вызовет исключение одновременной модификации:
источник
Чтобы расширить собственный ответ Пола, он продемонстрировал, что байт-код одинаков на этом конкретном компиляторе (предположительно, в javac от Sun?), Но разные компиляторы не гарантируют генерацию одного и того же байт-кода, верно? Чтобы увидеть разницу между ними, давайте сразу перейдем к источнику и проверим Спецификацию языка Java, в частности 14.14.2, «Улучшенный оператор for» :
Другими словами, JLS требует, чтобы эти два были эквивалентны. Теоретически это может означать незначительные различия в байт-коде, но в действительности расширенный цикл for необходим для:
.iterator()
метод.hasNext()
.next()
Таким образом, другими словами, для всех практических целей байт-код будет идентичен или почти идентичен. Трудно представить какую-либо реализацию компилятора, которая привела бы к какой-либо значительной разнице между ними.
источник
foreach
Подкапотное создающемуiterator
, называя hasNext () и вызов следующей () , чтобы получить значение; Проблема с производительностью возникает только в том случае, если вы используете что-то, что реализует RandomomAccess.Проблемы с производительностью цикла, основанного на итераторах, заключаются в следующем:
Iterator<CustomObj> iter = customList.iterator();
);iter.hasNext()
во время каждой итерации цикла происходит виртуальный вызов invokeInterface (просмотрите все классы, затем выполните поиск таблицы методов перед переходом).hasNext()
вызова было равно значению: # 1 получить текущий счет и # 2 получить общий счетiter.next
(так что: просмотрите все классы и выполните поиск таблицы методов перед переходом), а также должен выполнить поиск по полям: # 1 получить индекс, а # 2 получить ссылку на массив, чтобы сделать смещение в него (в каждой итерации).Потенциальная оптимизация состоит в том, чтобы переключиться на
index iteration
поиск с кэшированным размером:Здесь мы имеем:
customList.size()
при первоначальном создании цикла for для получения размераcustomList.get(x)
во время тела для цикла, который является полем поиска в массиве, а затем может сделать смещение в массивеМы сократили тонну вызовов методов, полевых поисков. Это вы не хотите делать с
LinkedList
чем-то или с чем-то, что не являетсяRandomAccess
объектом коллекции, иначеcustomList.get(x)
оно превратится во что-то, что должно проходить черезLinkedList
каждую итерацию.Это идеально, если вы знаете, что это любая
RandomAccess
коллекция списков.источник
foreach
все равно использует итераторы под капотом. Это действительно просто синтаксический сахар.Рассмотрим следующую программу:
Давайте скомпилировать его с
javac Whatever.java
,и читать разобранные байты - код
main()
, с помощьюjavap -c Whatever
:Мы можем видеть, что это
foreach
сводится к программе, которая:List.iterator()
Iterator.hasNext()
: вызываетIterator.next()
и продолжает циклЧто касается того, «почему этот бесполезный цикл не оптимизируется из скомпилированного кода? Мы видим, что он ничего не делает с элементом списка»: хорошо, вы можете написать свой повторяемый код,
.iterator()
имеющий побочные эффекты. или так, что.hasNext()
имеет побочные эффекты или значимые последствия.Вы можете легко представить, что итератор, представляющий прокручиваемый запрос из базы данных, может сделать что-то существенное
.hasNext()
(например, связаться с базой данных или закрыть курсор, потому что вы достигли конца набора результатов).Таким образом, хотя мы можем доказать, что в теле цикла ничего не происходит ... это дороже (неразрешимо?) Доказать, что ничего существенного / последовательного не происходит, когда мы выполняем итерацию. Компилятор должен оставить это пустое тело цикла в программе.
Лучшее, на что мы могли надеяться, это предупреждение компилятора . Интересно , что
javac -Xlint:all Whatever.java
это не предупреждает нас об этом пустом теле цикла. IntelliJ IDEA делает, хотя. По общему признанию я настроил IntelliJ для использования Eclipse Compiler, но это не может быть причиной этого.источник
Iterator - это интерфейс в платформе Java Collections, который предоставляет методы для обхода или перебора коллекции.
Итератор и цикл for действуют аналогично, когда ваш мотив состоит в том, чтобы просто пройти по коллекции, чтобы прочитать ее элементы.
for-each
это всего лишь один из способов перебора коллекции.Например:
И цикл for-each может использоваться только для объектов, реализующих интерфейс итератора.
Теперь вернемся к случаю цикла for и итератора.
Разница возникает, когда вы пытаетесь изменить коллекцию. В этом случае итератор более эффективен из-за своего свойства fail-fast . то есть. он проверяет наличие каких-либо изменений в структуре базовой коллекции перед тем, как перейти к следующему элементу. Если будут найдены какие-либо изменения, будет выдано исключение ConcurrentModificationException .
(Примечание: эта функциональность итератора применима только в случае классов коллекций в пакете java.util. Она не применима для параллельных коллекций, поскольку они по своей природе отказоустойчивы)
источник
Мы должны избегать использования традиционного цикла for при работе с коллекциями. Простая причина, которую я приведу, состоит в том, что сложность цикла for имеет порядок O (sqr (n)), а сложность Iterator или даже расширенного цикла for равна O (n). Таким образом, это дает разницу в производительности. Просто возьмите список из примерно 1000 наименований и распечатайте его обоими способами. а также распечатать разницу во времени для исполнения. Вы можете увидеть разницу.
источник