Java 5給了我們for-each循環,應該儘可能使用它。迭代數組的哪個習慣用法最有效?
但是,如果您需要使用塊中數組的索引,那麼最有效的習慣用法是什麼?
// Option (1)
for (int i = array.length - 1; i >= 0; i--) {
// Code.
}
// Option (2)
for (int i = 0; i < array.length; i++) {
// Code.
}
// Option (3)
for (int i = 0, n = array.length; i < n; i++) {
// Code.
}
(很顯然,這不會使大多數程序,但幽默我有很大的性能差異。):-)
向後循環,是可怕的。甚至可能緩存不友好?或者現代化的處理器能夠檢測到內存中的回退?
更短,我可以看到JIT編譯器如何確定
array
從不改變,因此length
是恆定的,因此它基本上可以用(3)代替它。但是這樣做嗎? (假設JVM是Oracle的Hotspot/Java 7)由Joshua Bloch在項目45中建議的Effective Java作爲最快的選項,如果它是一些
Collection.size()
這是上限。但是它也適用於數組嗎?從字節碼(見下面)我可以看出,每個週期保存一個arraylength
指令(預優化)。
This question有關的Dalvik虛擬機循環,列表(1) - (3)最快到最慢。然而,這些信息是從2008年開始的,今天Dalvik更加成熟,所以我幾乎認爲情況依然如此。
看着從上面的例子中生成的字節碼,有明顯的差異:
Compiled from "ForLoops.java"
public class ForLoops extends java.lang.Object{
static int[] array;
public ForLoops();
Code:
0: aload_0
1: invokespecial #10; //Method java/lang/Object."<init>":()V
4: return
public static void forLoop1();
Code:
0: getstatic #17; //Field array:[I
3: arraylength
4: iconst_1
5: isub
6: istore_0
7: goto 13
10: iinc 0, -1
13: iload_0
14: ifge 10
17: return
public static void forLoop2();
Code:
0: iconst_0
1: istore_0
2: goto 8
5: iinc 0, 1
8: iload_0
9: getstatic #17; //Field array:[I
12: arraylength
13: if_icmplt 5
16: return
public static void forLoop3();
Code:
0: iconst_0
1: istore_0
2: getstatic #17; //Field array:[I
5: arraylength
6: istore_1
7: goto 13
10: iinc 0, 1
13: iload_0
14: iload_1
15: if_icmplt 10
18: return
}
理論上,1和3在一般情況下效率更高,因爲'array.length'只需要進行一次評估。然而,在Java中評估'length'非常簡單,尤其是在JITCed的情況下,所以理論上的差異在實踐中幾乎消失了。最有可能取決於循環體中的內容以及JITC優化器如何將循環體與循環控制融合在一起。 –
@Nicholas尼斯工作拉出字節碼!但重要的是要記住,由於JIT,字節碼並不是最終執行的。如果我們可以得到JIT指令,那*就是最終答案,但這可能是不現實的,因爲(a)它是平臺特定的,(b)JITing有不同的「級別」。另外,我不知道任何JIT會給運行時生成的代碼提供任何透明度,至少不是沒有自己在JVM內存空間中進行操作。 – sigpwned
我當然應該指出,「最有效」的選擇通常是最適合其他代碼的選擇。例如,向後迭代有時很笨拙,有時非常有用。同樣,如果數組對象在循環內部被替換,在每次迭代中檢查'array.length'實際上可能是必需的。 –