Производительность традиционного для цикла против Iterator/foreach в Java

Есть ли результаты тестирования производительности, доступные при сравнении традиционных для цикла с Iterator при обходе ArrayList, HashMap и других коллекций?

Или просто зачем использовать Iterator для цикла или наоборот?

Ответ 1

Предполагая, что это вы имели в виду:

// traditional for loop
for (int i = 0; i < collection.size(); i++) {
  T obj = collection.get(i);
  // snip
}

// using iterator
Iterator<T> iter = collection.iterator();
while (iter.hasNext()) {
  T obj = iter.next();
  // snip
}

// using iterator internally (confirm it yourself using javap -c)
for (T obj : collection) {
   // snip
}

Итератор работает быстрее для коллекций без произвольного доступа (например, TreeSet, HashMap, LinkedList). Для массивов и ArrayLists различия производительности должны быть незначительными.

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

  • итерация по LinkedList и ArrayList соответственно
  • со 100 000 "случайными" строками
  • суммирование их длины (просто что-то, чтобы избежать того, что компилятор оптимизирует весь цикл)
  • используя все три стиля цикла (итератор, для каждого, для счетчика)

Результаты аналогичны для всех, кроме "для счетчика" с LinkedList. Все остальные пять заняли менее 20 миллисекунд, чтобы перебрать весь список. Использование list.get(i) в LinkedList 100 000 раз заняло более 2 минут (!) Для завершения (в 60 000 раз медленнее). Вау!:) Следовательно, лучше использовать итератор (явно или неявно используя для каждого), особенно если вы не знаете, какой тип и размер списка вы имеете дело.

Ответ 2

Первой причиной использования итератора является очевидная правильность. Если вы используете индекс ручного индекса, могут быть очень безобидные ошибки, которые вы можете увидеть, только если вы посмотрите очень внимательно: вы начали с 1 или 0? Вы закончили с length - 1? Вы использовали < или <=? Если вы используете итератор, гораздо проще увидеть, что он действительно выполняет итерацию всего массива. "Скажи, что ты делаешь, делай то, что говоришь".

Вторая причина - единообразный доступ к различным структурам данных. Доступ к массиву можно получить эффективно через индекс, но связанный список лучше всего отслеживать, запоминая последний доступный элемент (в противном случае вы получите " Шлемиэль художник" ). Еще более сложный хэш файл. Предоставляя единый интерфейс из этих и других структур данных (например, вы также можете выполнять обходы дерева), вы снова получаете ясную правильность. Логика перемещения должна быть реализована только один раз, и используемый ею код может кратко "сказать, что он делает, и делать то, что он говорит".

Ответ 3

В большинстве случаев производительность аналогична.

Однако, всякий раз, когда код получает список и на него накладывается петля, существует хорошо известный случай:
Итератор лучше подходит для всех реализаций List, которые не реализуют RandomAccess (пример: LinkedList).

Причина в том, что для этих списков доступ к элементу по индексу не является постоянной операцией времени.

Итак, вы также можете считать Iterator более надежным (для деталей реализации).


Как всегда, производительность не должна быть скрытой. Jave5 foreach loop является большим хитом в этом аспекте: -)

Ответ 4

Я не считаю, что

for (T obj : collection) {

рассчитывает .size() каждый раз через цикл и поэтому быстрее, чем

for (int i = 0; i < collection.size(); i++) {

Ответ 5

Используйте JAD или JD-GUI против вашего сгенерированного кода, и вы увидите, что нет никакой реальной разницы. Преимущество новой формы итератора заключается в том, что он выглядит более чистым в вашей кодовой базе.

Изменить. Я вижу из других ответов, что вы на самом деле имели в виду разницу между использованием get (i) итератором. Я взял оригинальный вопрос, чтобы обозначить разницу между старыми и новыми способами использования итератора.

Использование get (i) и поддержание собственного счетчика, особенно для классов List, не является хорошей идеей по причинам, упомянутым в принятом ответе.

Ответ 6

Одна из лучших причин использования итератора по синтаксису я ++ заключается в том, что не все структуры данных будут поддерживать произвольный доступ, если не считать того, что он хорошо работает. Вы также должны программировать интерфейс списка или коллекции, чтобы, если позже вы решите, что другая структура данных будет более эффективной, вы сможете ее заменять без массивной операции. В этом случае (случай кодирования для интерфейса) вы не обязательно будете знать детали реализации, и, вероятно, разумнее отложить это на самой структуре данных.

Ответ 7

Одна из причин, по которой я научился придерживаться каждого из них, заключается в том, что он упрощает вложенные циклы, особенно над 2+ мерными циклами. Все i, j и k, с которыми вы можете столкнуться, могут очень запутываться.

Ответ 8

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

Метод get (i) в связанном списке начинается с головы node и перемещается по ссылкам вплоть до i'th node. Когда вы перебираете связанный список с использованием традиционного цикла for, вы начинаете снова с головы node каждый раз, поэтому общий обход становится квадратичным.

for( int i = 0; i< list.size(); i++ ) {
    list.get(i); //this starts everytime from the head node instead of previous node
}

Пока для каждого цикла выполняется итератор по итератору, полученному из связанного списка, и вызывает его метод next(). Итератор поддерживает состояния последнего доступа и, таким образом, не запускается полностью из головы каждый раз.

for( Object item: list ) {
    //item element is obtained from the iterator next method.
}

Ответ 9

+1 к чему сказал сфусенеггер. FYI, независимо от того, используете ли вы явный итератор или неявный (т.е. Для каждого), не будет иметь разницу в производительности, поскольку они скомпилируются с одним и тем же байтовым кодом.