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


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

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

9 55

9 ответов:

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

// 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 различия в производительности должны быть незначительными.

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

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

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

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

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

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

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

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

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


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

Я в это не верю

for (T obj : collection) {

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

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

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

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

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

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

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

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

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

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's next method.
}

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