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

Posted

技术标签:

【中文标题】看似无限循环终止,除非使用 System.out.println【英文标题】:Seemingly endless loop terminates, unless System.out.println is used 【发布时间】:2017-06-17 17:55:33 【问题描述】:

我有一段简单的代码假设是一个无限循环,因为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是否识别出它的无限循环并在第一种情况下跳过它,但必须在第二种情况下执行方法调用才能按预期运行? 困惑:)

【问题讨论】:

第二个循环是无限的,因为上限x 比循环变量j 增长。换句话说,j 永远不会达到上限,因此循环将“永远”运行。好吧,不是永远,你很可能会在某个时候溢出。 这不是一个死循环,只是第一种情况需要循环238609294次才能退出for循环,第二次打印y的值238609294次跨度> 一句话回答:溢出 有趣的是,System.out.println(x) 而不是最后的y 会立即显示问题所在 @TeroLahtinen 不,它不会。如果您对 int 类型有疑问,请阅读 Java 语言规范。它独立于硬件。 【参考方案1】:

这两个例子都不是无穷无尽的。

问题在于 Java(或几乎任何其他通用语言)中 int 类型的限制。当x的值达到0x7fffffff时,任何正值相加都会导致溢出,x变为负数,因此低于j

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

正如讨论中提到的,时间在很大程度上取决于操作系统如何缓冲输出,是否输出到终端仿真器等,所以它可能比几分钟长得多。

【讨论】:

我刚刚尝试了一个程序(在我的笔记本电脑上)循环打印一行。我对它进行了计时,它能够打印大约 1000 行/秒。根据 N00b 的评论,循环将执行 238609294 次,循环终止大约需要 23861 秒 - 超过 6.6 小时。比“几分钟”多一点。 @ajb:取决于实现。 Windows 上的 IIRC println() 是一个阻塞操作,而在(一些?)Unix 上它是缓冲的,所以运行得更快。还可以尝试使用print(),它会缓冲直到遇到\n(或缓冲区已满,或调用flush() 还取决于显示输出的终端。极端例子见***.com/a/21947627/53897(减速是由于自动换行) 是的,它在 UNIX 上已缓冲,但仍处于阻塞状态。一旦 8K 左右的缓冲区填满,它就会阻塞,直到有空间为止。速度将在很大程度上取决于消耗的速度。将输出重定向到 /dev/null 将是最快的,但是将其发送到终端,默认情况下,将需要屏幕上的图形更新和更多的计算能力,因为它会降低字体的渲染速度。 @Zbynek 哦,可能是这样,但这提醒我,终端 I/O 通常是行缓冲的,而不是阻塞的,所以很可能每个 println 都会导致系统调用进一步减慢终端速度案例。【参考方案2】:

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

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

【讨论】:

人们没有意识到向控制台发送多少垃圾邮件会降低他们的代码速度。【参考方案3】:

这可能有两个原因:

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

    Java 可能实际上并没有优化循环并且它正在正确执行程序,最终x 将变得太大而无法满足int 并溢出。整数溢出很可能会使整数x 为负数,小于j,因此它将退出循环并打印y 的值。这也可以通过在循环后添加System.out.println(x); 来检查。

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

【讨论】:

我选择2号门。 是的。它进入负数并退出循环。但是sysout 太慢了,以至于添加了无限循环的错觉。 1.将是一个错误。编译器优化不允许改变程序的行为。如果这是一个无限循环,那么编译器可能会优化它想要的一切,但是,结果必须仍然是一个无限循环。真正的解决方案是 OP 弄错了:两者都不是无限循环,一个只是比另一个做更多的工作,所以需要更长的时间。 @JörgWMittag 在这种情况下 x 是一个局部变量,与其他任何东西都没有关系。因此,它有可能被优化掉。但是应该查看字节码以确定是否是这种情况,永远不要仅仅假设编译器做了类似的事情。【参考方案4】:

它们都不是无限循环, 初始 j = 0 ,只要 j

如果您正在寻找无限循环的示例, 它应该是这样的

int x = 6;

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

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

您还可以使用双 for 循环创建无限循环:

int i ;

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

这个循环是无限的,因为第一个 for 循环说 i

【讨论】:

【参考方案5】:

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

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

你觉得它变得无限的原因是System.out.println(y); 使代码的执行速度比没有任何操作时要慢得多。

【讨论】:

【参考方案6】:

有趣的问题实际上在这两种情况下循环都不是无穷无尽的

但它们之间的主要区别在于它将何时终止以及x 需要多长时间才能超过最大int 值,即2,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's answer 的有效性,也将是这个question 的答案。

【讨论】:

以上是关于看似无限循环终止,除非使用 System.out.println的主要内容,如果未能解决你的问题,请参考以下文章

Java 跳出For循环总结

Java 跳出For循环总结

深入理解字节码理解invokeSuper无限循环的原因

四月九号java知识

为啥这会导致无限循环

检查空无限循环中的选项与做一些无限循环