看似无限循环终止,除非使用 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 上的 IIRCprintln()
是一个阻塞操作,而在(一些?)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的主要内容,如果未能解决你的问题,请参考以下文章