Который более эффективен, цикл foreach или итератор?

Который является самым эффективным способом пересечь набор?

List  a = new ArrayList();
for (Integer integer : a) {
  integer.toString();
}

или

List  a = new ArrayList();
for (Iterator iterator = a.iterator(); iterator.hasNext();) {
   Integer integer = (Integer) iterator.next();
   integer.toString();
}

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

Как предложено на Meta я буду отправлять свой ответ на этот вопрос.

196
задан Community 23 May 2017 в 02:02
поделиться

3 ответа

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

Если, однако, вы подразумеваете с циклом Loop Старый «C-стиль» цикла:

for(int i=0; i<list.size(); i++) {
   Object o = list.get(i);
}

, затем новый для цикла или итератора, может быть намного более эффективным, в зависимости от основной структуры данных. Причина этого состоит в том, что для некоторых структур данных GET (I) представляет собой операцию O (n), которая делает петлю o (n 2 ) операцию. Традиционный связанный список является примером такой структуры данных. Все итераторы имеют в качестве фундаментального требования, что следующий () должен быть операцией O (1), что делает цикл O (n).

Чтобы убедиться, что итератор используется под водой по новой для синтаксиса цикла, сравните сгенерированные байты из следующих двух фрагментов Java. Сначала для цикла:

List<Integer>  a = new ArrayList<Integer>();
for (Integer integer : a)
{
  integer.toString();
}
// Byte code
 ALOAD 1
 INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator;
 ASTORE 3
 GOTO L2
L3
 ALOAD 3
 INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object;
 CHECKCAST java/lang/Integer
 ASTORE 2 
 ALOAD 2
 INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String;
 POP
L2
 ALOAD 3
 INVOKEINTERFACE java/util/Iterator.hasNext()Z
 IFNE L3

и второй, итератор:

List<Integer>  a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();)
{
  Integer integer = (Integer) iterator.next();
  integer.toString();
}
// Bytecode:
 ALOAD 1
 INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator;
 ASTORE 2
 GOTO L7
L8
 ALOAD 2
 INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object;
 CHECKCAST java/lang/Integer
 ASTORE 3
 ALOAD 3
 INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String;
 POP
L7
 ALOAD 2
 INVOKEINTERFACE java/util/Iterator.hasNext()Z
 IFNE L8

Как вы можете видеть, сгенерированный байтовый код эффективно идентичен, поэтому не существует штрафа производительности для использования любой формы. Следовательно, вы должны выбрать форму цикла, которая наиболее эстетически привлекательна для вас, для большинства людей, которые будут за каждый цикл, так как у нас меньше кода котел.

255
ответ дан 23 November 2019 в 05:18
поделиться

Разница не в производительности, а в возможностях. При использовании ссылки напрямую у вас есть больше мощности с использованием типа ITERATOR (E.G. LISTER.ITRATORATOR () vs. List.Listiterator (), хотя в большинстве случаев они возвращают ту же реализацию). У вас также есть возможность ссылаться на итератор в свой цикл. Это позволяет вам делать такие вещи, как удалить элементы из вашей коллекции, не получая ConsullentModificationException.

E.G.

Это нормально:

Set<Object> set = new HashSet<Object>();
// add some items to the set

Iterator<Object> setIterator = set.iterator();
while(setIterator.hasNext()){
     Object o = setIterator.next();
     if(o meets some condition){
          setIterator.remove();
     }
}

Это не так, как он бросит одновременную модификацию исключения:

Set<Object> set = new HashSet<Object>();
// add some items to the set

for(Object o : set){
     if(o meets some condition){
          set.remove(o);
     }
}
104
ответ дан 23 November 2019 в 05:18
поделиться

В дополнение к собственному ответу Пола, он показал, что байткод на этом конкретном компиляторе одинаков (предположительно, на javac Sun?), но разные компиляторы не гарантируют генерацию одного и того же байткода, не так ли? Чтобы увидеть, в чем разница между ними, давайте сразу же обратимся к источнику и проверим Спецификацию языка Java, а именно 14.14.2, "The enhanced for statement":

Улучшенное утверждение для эквивалентно основному для утверждения формы:

for (I #i = Expression.iterator(); #i.hasNext(); ) {
    VariableModifiers(opt) Type Identifier = #i.next();    
    Statement 
}

Другими словами, JLS требует, чтобы эти два утверждения были эквивалентны. В теории это может означать предельные различия в байткоде, но в реальности для цикла требуется:

  • Вызвать метод .iterator()
  • Использовать .hasNext()
  • Сделать локальную переменную доступной через .next()

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

22
ответ дан 23 November 2019 в 05:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: