【问题标题】:Java For Loop differencesJava For 循环的区别
【发布时间】:2011-07-19 04:33:51
【问题描述】:

这些说法在 Java 中的内存占用和效率方面是否完全相同?

首先:

Object[] array = new Object[100];
int i = 0;
for (; i < array.length; i++){
   Object o = array[i];
   //do something
}

第二:

Object[] array = new Object[100];
for (int i = 0; i < array.length; i++){
   Object o = array[i];
   //do something
}

第三:

Object[] array = new Object[100];
for (Object o : array){
   //do something
}

【问题讨论】:

    标签: java loops for-loop foreach


    【解决方案1】:

    第一个不是常用的成语;我不会那样写的。

    没有内存或效率差异。第三个是在后来的 JVM 中添加的语法糖(我相信它是 JDK 6)。

    您的代码将成为内存和效率瓶颈,而不是您的循环构造。

    【讨论】:

    • 第一个习语在你想在特定条件下跳出循环并且稍后需要知道是否有中断以及如果是触发的对象的索引的情况下非常有用它。
    【解决方案2】:

    在内存占用和效率方面,是的。但是,也有区别。首先,i 存在(具有范围)超出循环;而在第二个它没有。在第三种情况下,没有(直接)方法可以访问索引或更改当前对象位置的数组内容。

    【讨论】:

    • 非常感谢。那么在第二个语句中,我将只为迭代器分配一个内存?还是每次迭代都有不同的分配?第三个... Java分配了一个隐藏的迭代器?以及数组长度的隐藏整数?
    • 没错。如果您喜欢冒险,您可以使用 javap 查看每种循环生成的字节码。它们应该非常接近相同。
    • 哦,我不知道 javap... 再次感谢您! :)
    • @Oneiros - 我错过了你之前评论的一部分。每次循环都会重新评估数组长度。如果您为array 分配不同的数组,循环测试将遵循新的长度。通过将数组长度分配给局部变量并在测试中使用它(或者通过从array.length-1 向下迭代到0),可以节省(非常)微小的节省。我很少为了优化而引入额外的变量来存储数组长度。
    【解决方案3】:

    Java 5 引入了第三个版本,旨在简化您使用泛型的工作。它得到了增强,因为您不需要在循环之前确定数组中有多少元素。也无需指定如何增加当前位置,提供更简洁的实现,而无需创建计数器变量或迭代器。

    【讨论】:

    • 我同意增强的 for 循环语法更简洁,前提是您不需要循环体中的当前索引。 (一方面,您不必担心正文是否与索引变量混淆。)不过,在我看来,它可以更容易地掩盖意外的自动装箱开销(尽管在这种特殊情况下不是)。
    【解决方案4】:

    不,它不完全一样。这很容易验证,恕我直言,甚至没有那么令人惊讶。

    只需反编译以下两个函数:

    public static void test1(Object[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
    
    public void test2(Object[] arr) {
        for(Object o : arr) {
            System.out.println(o);
        }
    }
    

    并查看输出:

    public static void test1(java.lang.Object[]);
      Code:
       0:   iconst_0
       1:   istore_1
       2:   iload_1
       3:   aload_0
       4:   arraylength
       5:   if_icmpge       23
       8:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
       11:  aload_0
       12:  iload_1
       13:  aaload
       14:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
       17:  iinc    1, 1
       20:  goto    2
       23:  return
    
    public void test2(java.lang.Object[]);
      Code:
       0:   aload_1
       1:   astore_2
       2:   aload_2
       3:   arraylength
       4:   istore_3
       5:   iconst_0
       6:   istore  4
       8:   iload   4
       10:  iload_3
       11:  if_icmpge       34
       14:  aload_2
       15:  iload   4
       17:  aaload
       18:  astore  5
       20:  getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
       23:  aload   5
       25:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
       28:  iinc    4, 1
       31:  goto    8
       34:  return
    }
    

    我刚刚包含了 println(),以便我们看到变量已经完成,并确保 javac 不会优化它。显然,从更大的角度来看,差异并不重要,它们几乎无法衡量,但仍然不是相同的代码;)

    虽然我不确定第二个函数到底发生了什么,所以如果有人想花时间剖析代码,请继续;-)

    【讨论】:

    • 您的第一个示例并不完全跟踪 OP 的第一个示例。您需要将System.out.println(arr[i]); 替换为Object o = arr[i]; System.out.println(o); 以进行公平比较。所以只有在test2 中,每个数组元素都存储在o(局部变量#5)中。第二个也做了一些不同的事情:它正在缓存数组的长度(在局部变量#3中)并将arr复制到另一个局部变量(#2)。这些差异可能是由于编译器应用了不同的字节码模板。
    猜你喜欢
    • 2021-09-17
    • 2015-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-11
    • 2021-05-12
    • 1970-01-01
    相关资源
    最近更新 更多