我有这个循环
for (it= someCollection.iterator; it.hasNext(); )
{
//some code here
}
我把它改为:
for (it= someCollection.iterator;; )
{
if (!it.hasNext())
break;
//some code here
}
第二个代码在Eclipse上的junit中的单元测试中跑得快一点。 第二个循环更快吗?我问,因为Junit给出的时间不是太精确,但它们给出了近似值
答案 0 :(得分:3)
在研究这类问题时,根据块控制流程图来考虑生成的字节码是有用的,其中块是一个字节码指令序列,只能从第一条指令输入,只留在其后最后一条指令(省略退出以简化问题)。
使用此示例代码:
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()
的边缘称为后边缘,因为它将节点链接到其中一个支配者。这正式定义了循环是什么。 for
- 循环(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
编译的,如果使用Eclipse编译器进行编译,你可能会得到一些稍微不同的东西,因为示例):
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
之后转到块{1}}(25,相当于另一个中的22)并使用goto
来结束。这实际上是等效的,现代JIT编译器应该毫无困难地优化这个小差异。除此之外,你的两个循环完全相同。
我不确定你是如何进行测量的,但是你也应该意识到这不是因为System.nanoTime()
给你一个纳秒的结果,它具有该顺序的分辨率,远非如此。高分辨率定时器很难实现,并且取决于硬件和操作系统。见JavaDoc:
此方法提供纳秒精度,但不一定 纳秒分辨率(即,值的变化频率) - 否 除了分辨率至少与分辨率一样好之外,还要做出保证 currentTimeMillis()。
如果你没有得到足够高的差异,你可能不会得到与分辨率相比更重要的东西。
答案 1 :(得分:2)
我希望它们能够编译成相同的字节码。
答案 2 :(得分:0)
他们应该在同一时间执行。
有什么用?
while(it.hasNext())
答案 3 :(得分:0)
这两个例子完全相同。
注意:由于循环中没有更新语句,类似于it.next()
这两个循环可能会永远运行,除非它们只有没有元素。
或者你把它们放在这里只是为了说明。
答案 4 :(得分:0)
循环语法
for(initialization; Boolean_expression; update)
{
//Statements
}
首先执行初始化步骤,并且只执行一次。此步骤允许您声明和初始化任何循环控制变量。只要出现分号,就不需要在此处添加声明。
接下来,评估布尔表达式。如果是,则执行循环体。如果为false,则循环体不执行,控制流跳转到for循环之后的下一个语句。
执行for循环体之后,控制流会跳回到update语句。此语句允许您更新任何循环控制变量。只要在布尔表达式后面出现分号,此语句就可以留空。
现在再次评估布尔表达式。如果为真,则循环执行并且过程重复(循环体,然后更新步骤,然后是布尔表达式)。布尔表达式为false后,for循环终止。
在第二个代码中,如果条件为真,则不执行第4步。所以它比第一代码更快。