почему улучшенный цикл более эффективен, чем нормальный цикл

Я читал, что улучшенный цикл for более эффективен, чем обычный для цикла:

http://developer.android.com/guide/practices/performance.html#foreach

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

for(Integer i : list){
   ....
}


int n = list.size();
for(int i=0; i < n; ++i){
  ....
}

Но является ли это единственной причиной, улучшенная для цикла лучше, чем нормальная для цикла? В этом случае лучше использовать нормальную для цикла из-за небольшой сложности в понимании расширенного цикла.

Проверьте это на интересную проблему: http://www.coderanch.com/t/258147/java-programmer-SCJP/certification/Enhanced-Loop-Vs-Loop

Может ли кто-нибудь объяснить внутреннюю реализацию этих двух типов циклов или объяснить другие причины использования расширенного цикла?

Ответы

Ответ 1

Немного упростить, чтобы сказать, что усиленный цикл более эффективен. Это может быть, но во многих случаях это почти точно так же, как петля старой школы.

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

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

LinkedList<Object> list = ...;

// Loop 1:
int size = list.size();
for (int i = 0; i<size; i++) {
   Object o = list.get(i);
   /// do stuff
}

// Loop 2:
for (Object o : list) {
  // do stuff
}

// Loop 3:
Iterator<Object> it = list.iterator();
while (it.hasNext()) {
  Object o = it.next();
  // do stuff
}

В этом случае Loop 1 будет медленнее, чем Loop 2 и Loop 3, потому что он должен (частично) пересекать список на каждой итерации, чтобы найти элемент в позиции i. Loop 2 и 3, однако, будут только перешагивать один элемент дальше в списке из-за использования Iterator. Loop 2 и 3 также будут иметь почти такую ​​же производительность, поскольку Loop 3 в значительной степени соответствует тому, что создаст компилятор при написании кода в Loop 2.

Ответ 2

Я сам удивлен небольшим экспериментом, который я сделал сегодня с вышеупомянутыми пунктами. Так что я сделал, так это то, что я вставил определенное количество элементов в связанный список и повторил его с помощью вышеупомянутых трех методов: 1) используя расширенный для цикла 2), используя Iterator 3), используя простой цикл и get()
Я предполагаю, что программисты, такие как вы, ребята, можете лучше понять, что я сделал, увидев код.

long advanced_timeElapsed,iterating_timeElapsed,simple_timeElapsed;
    long first=System.nanoTime();
    for(Integer i: myList){
        Integer b=i;
    }
    long end= System.nanoTime();
    advanced_timeElapsed=end-first;
    System.out.println("Time for Advanced for loop:"+advanced_timeElapsed);
    first=System.nanoTime();
    Iterator<Integer> it = myList.iterator();
    while(it.hasNext())
    {
        Integer b=it.next();
    }
    end= System.nanoTime();
    iterating_timeElapsed=end-first;
    System.out.println("Time for Iterating Loop:"+iterating_timeElapsed);
    first=System.nanoTime();
    int counter=0;
    int size= myList.size();
    while(counter<size)
    {
        Integer b=myList.get(counter);
        counter++;  
    }
    end= System.nanoTime();
    simple_timeElapsed=end-first;
    System.out.println("Time for Simple Loop:"+simple_timeElapsed);

Результаты, где не то, что я ожидал. Ниже приведен график времени, прошедшего в 3 случаях. Time Elapsed Graph

Время оси Y X-осевой тест-тест
test case1:10 входов
тестовый корпус2: 30 входов
тестовый корпус3: 50 входов
test case4: 100 входов
test case5: 150 входов
тестовый корпус6: 300 входов
тестовый случай7: 500 входов
test case8: 1000 входов
тестовый случай9: 2000 входов
тестовый корпус 10: 5000 входов
тестовый случай11:10000 входов
тестовый случай12: 100000 входов

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

Ответ 3

Я читал, что улучшенный для цикла эффективный, чем нормальный для цикла.

На самом деле иногда это менее эффективно для программы, но в большинстве случаев это точно то же самое.

Более эффективен для разработчика, что часто гораздо важнее

А для каждого цикла особенно полезно при итерации по коллекции.

List<String> list = 
for(Iterator<String> iter = list.iterator(); list.hasNext(); ) {
    String s = list.next();

легче записывается как (но делает то же самое, что и его более эффективное для программы)

List<String> list = 
for(String s: list) {

Использование "старого" цикла немного более эффективно при доступе к случайно доступной коллекции по индексу.

List<String> list = new ArrayList<String>(); // implements RandomAccess
for(int i=0, len = list.size(); i < len; i++) // doesn't use an Iterator!

Использование цикла for для каждого цикла в коллекции всегда использует Iterator, который немного менее эффективен для списков произвольного доступа.

AFAIK, использование цикла for-each никогда не будет более эффективным для программы, но, как я уже сказал, эффективность разработчика часто гораздо важнее.

Ответ 4

Для каждого используется интерфейс Iterator. Я сомневаюсь, что он более эффективен, чем "старый" стиль. Итератору также необходимо проверить размер списка.

Это в основном для удобства чтения.

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

Ответ 5

Цикл foreach так же эффективен, как и этот тип цикла:

for (Iterator<Foo> it = list.iterator(); it.hasNext(); ) {
     Foo foo = it.next();
     ...
}

потому что он строго эквивалентен.

Если вы перебираете список с помощью

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

Тогда цикл foreach будет иметь эквивалентную производительность как ваш цикл, , но только для ArrayList. В случае LinkedList, ваш цикл будет иметь ужасную производительность, потому что на каждом итерации ему придется проходить все узлы списка до тех пор, пока он не достигнет элемента i th.

Цикл foreach (или цикл на основе итератора, который является тем же самым), не имеет этой проблемы, поскольку итератор ведет ссылку на текущий node и просто переходит к следующему на каждой итерации. Это выбор выборки, потому что он отлично работает со всеми типами списков. Он также четко выражает намерение и более безопасен, потому что вы не рискуете увеличивать индекс внутри цикла или использовать неверный индекс в случае вложенных циклов.

Ответ 6

Из эффективной Java:

Стандартная идиома для цикла через массив не обязательно приводит к избыточные проверки. Современные реализации JVM оптимизируют их.

Но Джош Блох не описывает, как JVM оптимизирует их.

Ответ 7

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

Для расширенного оператора можно использовать только до obtain array elements

он не может быть использован до modify elements.

Если вашей программе необходимо изменить элементы, используйте традиционный counter-controlled for.

Посмотрите

int[] array = { 1, 2, 3, 4, 5 };
for (int counter = 0; counter < array.length; counter++)
    array[counter] *= 2;

Мы не могли использовать оператор enhanced for, потому что были modifying the array’s elements.