Java for循环的效率

3
作为这个问题Java循环效率(“for” vs.“foreach”)的补充说明,
我有一个简单的问题。增强型for循环是否具有更大的内存占用量,或者它们都编译为相同的结构,使它们的内存占用量相同,因此使for(Object o : collection) { ... }始终比只读操作更好?
我提出这个问题的原因是,在我正在开发的模拟器中,每帧(60 /秒),我会针对可能包含数千个项的数组运行操作,例如要绘制的面,要进行光线追踪等。循环
for(Object o : collection) {
   o.doSomething(); 
}

看起来它可能会对集合进行内存复制(我似乎从之前的阅读中记得它确实这样做),在大多数情况下可以接受,但如果您每秒执行30000次光线追踪并且持续60秒,则不可行。

另一方面,循环显然是

count = collection.size();
for(i = 0; i < count; i++) {
   collection[i].doSomething();
}

这个程序一切都是通过引用来完成的,尽管它难以阅读(但老实说,也没有太多难度),占用空间相对较小。

有什么想法吗?

(注意:对于循环的难度影响只在几层嵌套后才会显现——对于单层循环,收益极小。我从经验中得出这个结论……例如:collection[i].property.subcollection[j].row[k].col[l].prop.subtable[m] 可能会让人崩溃,特别是如果其中一些需要强制转换:((Type3)((Type2)((Type1)collection[i]).property.subcollection[j].row[k]).col[l].prop).subtable[m]。)


2
我至少可以告诉你第二个实际上允许你在不破坏迭代器(即ConcurrentModificationException地狱)的情况下更改列表。这确实很有用。 - thatidiotguy
1
你的第二个循环无法编译。collection是一个数组还是一个集合类? - Lukas Eder
1
增强型for循环将被转换为基于迭代器的循环。因此,它可能会有稍微更高的开销。然而,JIT可以消除这种开销。 - jtahlborn
1
@RiverC - 除非你只使用Iterator.remove()... - jtahlborn
1
@RiverC:我只是想说,对于数组的 foreach 循环与对于 Iterable 的循环编译成的字节码是不同的。我猜这对你的问题很重要... - Lukas Eder
显示剩余5条评论
2个回答

2
如果您使用的是ArrayList,以下模式可能是一种微观优化。
for(int i=0, len=list.size(); i < len; i++)

使用for-each循环将始终创建Iterator,因此您保存的内存约为16-24个字节。

(每秒60次)我正在对数组中可能有数千个项目的操作进行运行,例如要绘制的面,射线跟踪等。

每秒60000次对于您来说不太可能会注意到差异,特别是由于您在处理每个项目时都会进行相对重要的工作。

看起来它可能会对集合进行内存复制。

它实际上没有这样做,这就是为什么如果您在遍历列表时更改列表会导致ConcurrentModificationException的原因之一。解决此问题的一种方法是复制列表。


好的,如果您每秒迭代60000个列表,则使用随机访问可能比使用for-each更好,因为它可以节省每次使用Iterator的时间。 - Peter Lawrey
迭代器会每次被销毁,还是如果不是数组,集合会重复使用它的迭代器? - user1086498
有时候我会使用数组,因为它们的内存开销较小 - 即在地图中的数据点可能会附加集合数据,所以我在那里使用数组来避免拥有成千上万的集合。 - user1086498
1
@RiverC 每次请求迭代器都会得到一个新的迭代器。这是必要的,因为它保留了您自己当前迭代的状态,即您目前所在的位置。 - user207421
所以,如果你正在处理许多小集合,最终会创建和处理大量的迭代器。这是一个需要注意的问题。 - user1086498
显示剩余2条评论

1

很难说JIT会如何优化您的代码(Java的哪个实现,哪个操作系统等)。这里的主要观点是for-each语法编译成与使用迭代器语法相同的字节码,请参见此处此处。使用迭代器语法的主要原因是在迭代期间删除项目。请注意,Iterator接口上的remove方法是可选的。

我做了一些简单的示例来查看字节码:

对于数组的每次迭代

// compiled .class file is 585 bytes
public class ForEachLoop {
  public static void main(String[] args) {
    for(String s : args){
      System.out.println(s);
    }
  }
}

// byte code for main method
public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=5, Args_size=1
   0:   aload_0
   1:   dup
   2:   astore  4
   4:   arraylength
   5:   istore_3
   6:   iconst_0
   7:   istore_2
   8:   goto    26
   11:  aload   4
   13:  iload_2
   14:  aaload
   15:  astore_1
   16:  getstatic   #16; //Field java/lang/System.out:Ljava/io/PrintStream;
   19:  aload_1
   20:  invokevirtual   #22; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   23:  iinc    2, 1
   26:  iload_2
   27:  iload_3
   28:  if_icmplt   11
   31:  return
  LineNumberTable: 
   line 4: 0
   line 5: 16
   line 4: 23
   line 7: 31

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      32      0    args       [Ljava/lang/String;
   16      7      1    s       Ljava/lang/String;

数组索引循环

// compiled .class file is 554 bytes
public class ArrayLoop {
  public static void main(String[] args) {
    for (int i = 0; i < args.length; i++) {
      System.out.println(args[i]);
    }
  }
}

// byte code for main method
public static void main(java.lang.String[]);
  Code:
   Stack=3, Locals=2, Args_size=1
   0:   iconst_0
   1:   istore_1
   2:   goto    17
   5:   getstatic   #16; //Field java/lang/System.out:Ljava/io/PrintStream;
   8:   aload_0
   9:   iload_1
   10:  aaload
   11:  invokevirtual   #22; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  iinc    1, 1
   17:  iload_1
   18:  aload_0
   19:  arraylength
   20:  if_icmplt   5
   23:  return
  LineNumberTable: 
   line 4: 0
   line 5: 5
   line 4: 14
   line 7: 23

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      24      0    args       [Ljava/lang/String;
   2      21      1    i       I

我们可以看到,数组迭代字节码更加紧凑(确切地说是减少了31个字节)。

对于每个循环遍历数组不是一个特殊情况吗?据我所知,没有涉及到迭代器,或者有吗? - jontro
数组的情况怎么样呢?(有时候我使用它们是因为它们占用较小的内存空间。) - user1086498
关于你的更新,非常酷!所以你使用更少的程序内存来执行普通的for循环。 - user1086498

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接