При изучении подобных проблем полезно подумать о сгенерированном байт-коде в терминах блок-графика потока блоков, где блок представляет собой последовательность команд байт-кода, которые могут быть введены только из первой инструкции и остаются только после его последняя инструкция (оставляя выходы для упрощения проблемы).
Используя этот пример кода:
for (Iterator it = c.iterator(); it.hasNext();) {
System.out.println(it.next());
}
System.out.println("Out");
Вы бы получить следующий график потока управления блоком. Я вернул эквивалентный байт-код в источник для удобочитаемости, но все инструкции, созданные System.out.println(it.next());
, принадлежат одному блоку, так как вы не можете прыгать посередине или выбраться из него.
Если вы проверить compiler book, вы обнаружите, что it.hasNext()
доминирует System.out.println(it.next())
, потому что вы должны пройти через it.hasNext()
ехать в System.out.println(it.next())
. Край от System.out.println(it.next())
до it.hasNext()
называется back-edge, потому что он связывает узел с одним из его доминантов. Это то, что формально определяет, что такое цикл. Первый оператор в for
-loop (Iterator it = c.iterator()
) фактически не принадлежит к циклу. Нет никакой разницы с циклом while, которому предшествует этот оператор, за исключением области объявленной переменной, но это не имеет значения после компиляции. Первый блок (it.hasNext()
) - это заголовок цикла.
Второй пример, как это будет производить тот же график:
for (Iterator it = c.iterator();;) {
if (!it.hasNext()) {
break;
}
System.out.println(it.next());
}
System.out.println("Out");
Основное отличие состоит в том, что могут быть некоторые бесполезные goto
заявления, в зависимости от стратегии компиляции.
Если посмотреть на сгенерированный байткод, используя javap -c
для этих двух примеров, вы получите это (это был скомпилирован с javac
, вы можете получить что-то немного другое, если вы компиляции с компилятором затмений, например):
public void loop1();
Code:
0: new #2; //class java/util/ArrayList
3: dup
4: invokespecial #3; //Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4; //Method java/util/ArrayList.iterator:()Ljava/util/Iterator;
12: astore_2
13: aload_2
14: invokeinterface #5, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
19: ifeq 37
22: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream;
25: aload_2
26: invokeinterface #7, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
31: invokevirtual #8; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
34: goto 13
37: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream;
40: ldC#9; //String Out
42: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
45: return
public void loop2();
Code:
0: new #2; //class java/util/ArrayList
3: dup
4: invokespecial #3; //Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4; //Method java/util/ArrayList.iterator:()Ljava/util/Iterator;
12: astore_2
13: aload_2
14: invokeinterface #5, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
19: ifne 25
22: goto 40
25: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream;
28: aload_2
29: invokeinterface #7, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
34: invokevirtual #8; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
37: goto 13
40: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream;
43: ldC#9; //String Out
45: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
48: return
Единственное отличие состоит в том, что первый использует ifeq 37
, чтобы перейти прямо в конец или перейти к следующему блоку (22), тогда как другой использует ifne
, чтобы перейти к блоку после goto
(25, что эквивалентно 22 в другое) и использует goto
, чтобы перейти в конец в противном случае. Это фактически эквивалентно, и современный JIT-компилятор должен без проблем оптимизировать эту небольшую разницу. Кроме того, ваши две петли точно такие же.
Я не уверен, как вы сделали свои измерения, но вы также должны знать, что это не потому, что System.nanoTime()
дает вам результат в наносекундах, что он имеет разрешение этого порядка, вдалеке от этого. Таймеры с высоким разрешением довольно сложно реализовать и будут зависеть от оборудования и ОС. См JavaDoc:
Этот метод обеспечивает точность наносекунд, но не обязательно разрешение наносекунд (то есть, как часто изменяется значение) - не гарантии не принимаются, за исключением, что разрешение по крайней мере так же хорошо, как , что из currentTimeMillis().
Скорее всего, если вы не получите достаточно высокой разницы, вы не получите ничего существенного по сравнению с разрешением.
Просто для удовольствия, также попробуйте 'for (Object item: someCollection) {/ * Некоторое число здесь * /}' –
Я думаю, что вы будете бороться, чтобы определить это, поскольку это может зависеть от JVM, машины и т. Д. И т. Д. –
Что вызвало вы хотите узнать, «что было быстрее»? –