似乎无限循环终止,除非使用System.out.println


91

我有一段简单的代码,应该认为是一个无休止的循环,因为x它将一直在增长,并且将始终大于j

int x = 5;
int y = 9;
for (int j = 0; j < x; j++) {
   x = x + y;
}
System.out.println(y);

但按原样,它可以打印y并且不会无限循环。我不知道为什么。但是,当我以以下方式调整代码时:

int x = 5;
int y = 9;
for (int j = 0; j < x; j++) {
    x = x + y;
    System.out.println(y);
}
System.out.println(y);

这变成一个无尽的循环,我不知道为什么。java是否会识别出一个无穷循环并在第一种情况下跳过该循环,而在第二种情况下必须执行一个方法调用,以使其表现出预期的效果?困惑:)


4
第二个循环是无限的,因为上限比循环变量x增长得快j。换句话说,j它将永远不会达到上限,因此循环将“永远”运行。好吧,不是永远,您很可能在某个时候出现溢出。
Tim Biegeleisen'2

75
这不是一个无休止的循环,只是在第一种情况下需要238609294次循环才能退出for循环,第二次它打印y238609294次值
N00b Pr0grammer

13
一个词的答案:溢出
qwr

20
有趣的是,System.out.println(x)而不是y在结束时会立即显示出问题所在
JollyJoker

9
@TeroLahtinen不,不会。如果您不确定什么是int类型,请阅读Java语言规范。它是独立于硬件的。
9ilsdx 9rvj 0lo

Answers:


161

这两个例子并不是无止境的。

问题是intJava(或几乎所有其他通用语言)中类型的限制。当值x达到时0x7fffffff,将任何正值相加都会导致溢出,并且x变为负值,因此小于j

第一个和第二个循环之间的区别是内部代码需要更多的时间,并且可能要花几分钟才能x溢出。对于第一个示例,它可能会花费不到第二秒的时间,或者很可能是代码会被优化器删除,因为它没有任何作用。

如讨论中所述,时间将很大程度上取决于OS如何缓冲输出,是否将其输出到终端仿真器等,因此它可能比几分钟要高得多。


48
我刚刚尝试了一个程序(在我的笔记本电脑上),该程序可以循环打印一行。我对它进行了计时,它能够打印大约1000行/秒。根据N00b的评论,循环将执行238609294次,终止循环大约需要6.6个小时,大约需要23861秒。比“几分钟”还多一点。
2017年

11
@ajb:取决于实现。println()Windows上的IIRC 是一项阻止操作,而在Unix上(某些?),它被缓冲了,因此运行得更快。还可以尝试使用print(),缓冲直到其命中一个\n(或缓冲填充或被flush()称为)
BlueRaja-Danny Pflughoeft

6
另外,它取决于显示输出的端子。看到stackoverflow.com/a/21947627/53897为一个极端的例子(其中减慢是由于自动换行)
托尔比约恩Ravn的安德森

1
是的,它已在UNIX上缓冲,但仍处于阻塞状态。一旦8K左右的缓冲区填满,它将阻塞直到有空间为止。速度将很大程度上取决于消耗的速度。将输出重定向到/ dev / null将是最快的,但是将其发送到终端(默认情况下)将需要在屏幕上更新图形,并需要更多的计算能力,因为它会使字体变慢。
penguin359'2

2
@Zbynek哦,也许是这样,但这提醒我,终端I / O通常将被行缓冲,而不是阻塞,因此每个println最有可能导致系统调用,从而进一步降低终端状态。
penguin359 '02

33

由于将它们声明为int,因此一旦达到最大值,则循环将中断,因为x值将变为负数。

但是,当将System.out.println添加到循环中时,执行速度就变得可见(因为输出到控制台将降低执行速度)。但是,如果让第二个程序(循环内有syso的程序)运行足够长的时间,则它应具有与第一个程序(循环内无syso的程序)相同的行为。


21
人们没有意识到向控制台发送大量垃圾邮件会减慢其代码速度。
user9993 '17

13

可能有两个原因:

  1. Java优化了for循环,由于循环x后没有使用,因此只需删除循环即可。您可以通过System.out.println(x);在循环后放置语句来检查此情况。

  2. Java可能没有真正优化循环,而是正确地执行了程序,最终x可能变得太大int而溢出。整数溢出很可能使整数x成为负数,该整数将小于j,因此它将退出循环并显示的值y。也可以通过System.out.println(x);在循环后添加来检查。

同样,即使在第一种情况下,最终也会发生溢出,从而将其呈现为第二种情况,因此它永远不会是一个真正的无限循环。


14
我选择门数2
罗比Cornelissen

真正。它以负数扩大并退出循环。但是a sysout是如此之慢,以至于给人以无限循环的错觉。
Pavan Kumar '02

4
1.将是一个错误。不允许通过编译器优化来更改程序的行为。如果这是一个无限循环,则编译器可以优化所需的所有内容,但是结果仍然必须是无限循环。真正的解决方案是OP错误:两者都不是无限循环,一个仅比另一个执行更多的工作,因此需要更长的时间。
约尔格W¯¯米塔格

1
@JörgWMittag在这种情况下,x是一个局部变量,与其他任何事物均无关。因此,有可能对其进行优化。但是,应该查看字节码以确定是否是这种情况,永远不要仅仅假设编译器做了类似的事情。
HopefulHelpful

1

它们都不是无穷循环,最初j = 0,只要j <x,j增加(j ++),并且j是整数,因此循环将运行直到达到最大值然后溢出(整数溢出是条件当算术运算(例如乘法或加法)的结果超过用于存储它的整数类型的最大大小时发生)。对于第二个示例,系统仅打印y的值,直到循环中断。

如果您正在寻找一个无限循环的例子,它应该看起来像这样

int x = 6;

for (int i = 0; x < 10; i++) {
System.out.println("Still Looping");
}

因为(x)永远不会达到10的值;

您还可以创建一个带有double for循环的无限循环:

int i ;

  for (i = 0; i <= 10; i++) {
      for (i = 0; i <= 5; i++){
         System.out.println("Repeat");   
      }
 }

此循环是无限的,因为第一个for循环说i <10,这是正确的,因此它进入第二个for循环,第二个for循环增加(i)的值,直到==5。然后进入第一个因为i <10,再次进行for循环,该过程不断重复,因为它在第二次for循环之后重置


1

这是一个有限循环,因为一旦x超过值2,147,483,647(即的最大值int),无论您是否打印y ,x都将变为负数且不大于j零。

您只需更改yto 的值100000y在循环中打印,循环将很快中断。

您之所以认为它变得无限,是因为与System.out.println(y);没有任何动作相比,使执行代码的速度非常慢。


0

有趣的问题实际上,在两种情况下循环都是无止境的

但是它们之间的主要区别在于何时终止以及x超过最大值需要多少时间int2,147,483,647之后最大值将达到溢出状态并终止循环。

理解此问题的最佳方法是测试一个简单的示例并保留其结果。

范例

for(int i = 10; i > 0; i++) {}
System.out.println("finished!");

输出:

finished!
BUILD SUCCESSFUL (total time: 0 seconds)

测试此无限循环后,将花费不到1秒的时间终止。

for(int i = 10; i > 0; i++) {
    System.out.println("infinite: " + i);
}
System.out.println("finished!");

输出:

infinite: 314572809
infinite: 314572810
infinite: 314572811
.
.
.
infinite: 2147483644
infinite: 2147483645
infinite: 2147483646
infinite: 2147483647
finished!
BUILD SUCCESSFUL (total time: 486 minutes 25 seconds)

在这个测试用例中,您会注意到终止和完成运行程序所花费的时间差异很大。

如果您没有耐心,您会认为此循环是无尽的,不会终止,但实际上要花费几个小时才能终止并达到i值的溢出状态。

最后,在将print语句放入for循环后,我们得出的结论是,比起没有print语句的第一种情况,循环所花的时间要多得多。

运行程序所花费的时间取决于您的计算机规格,特别是处理能力(处理器容量),操作系统和正在编译程序的IDE。

我在以下情况下测试这种情况:

联想2.7 GHz Intel Core i5

操作系统:Windows 8.1 64x

IDE:NetBeans 8.2

完成该程序大约需要8个小时(486分钟)。

您还可以注意到for循环中的步长增量 i = i + 1是达到最大int值的非常慢的因数。

我们可以更改此因子,使步长增加更快,以便在更短的时间内测试循环。

如果我们进行i = i * 10测试:

for(int i = 10; i > 0; i*=10) {
           System.out.println("infinite: " + i);
}
     System.out.println("finished!");

输出:

infinite: 100000
infinite: 1000000
infinite: 10000000
infinite: 100000000
infinite: 1000000000
infinite: 1410065408
infinite: 1215752192
finished!
BUILD SUCCESSFUL (total time: 0 seconds)

如您所见,与之前的循环相比,它非常快

终止并完成运行该程序不到1秒。

在这个测试示例之后,我认为它应该澄清问题并证明Zbynek Vyskovsky-kvr000答案的正确性,也将是对该问题的答案。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.