我有一个简单的代码,它应该是一个无止境的循环,因为x将一直在增长,并且始终比j大。
1 2 3 4 5 6
| int x = 5;
int y = 9;
for (int j = 0; j < x ; j ++) {
x = x + y ;
}
System. out. println(y ); |
但实际上,它打印的是y,不会无限循环。我不明白为什么。但是,当我按以下方式调整代码时:
1 2 3 4 5 6 7
| 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永远不会达到上限,因此循环将"永远"运行。好吧,不是永远,您很可能在某个点得到溢出。
- @Timbiegeleisen嗯?在第一个循环中,上界也比循环变量增长得更快,不是吗?
- @Timbiegeleisen唯一的区别是sysout。这是另外一回事。
- 它不是一个无止境的循环,只是第一次循环需要238609294次才能从for循环中出来,第二次它打印y238609294次的值。
- @n00bpr0克好捕获。工作时溢出。或者是下溢?:)
- @Robbycornelissen:)
- 两者都不是无限循环。seocond one print y untill x reached to max int值
- 一个字回答:溢出
- 在一台完美的(理论的/想象的)计算机中,它将永远运行。在现实生活中,你的变量是有限制的。
- 是否有一个工具来查看Java字节码,类似于C组件的GOOBET?然后可以判断循环是否执行。我找了找,但找不到任何方便的东西。
- 在C和C++循环中,BTW确实是未定义的行为,并且可以被优化出来(当然更糟的事情可能发生)。
- 有趣的是,到最后,System.out.println(x)而不是y会立即显示出问题所在。
- @Jollyjoker可能改变了编译器执行的优化,因为循环现在会产生副作用。
- @特罗拉蒂宁不,不会的。如果您怀疑什么是int类型,请阅读Java语言规范。它是硬件独立的。
- @弗朗西斯科多迪理论上是的,但实际上,他会看到一个负面的x,并立刻弄清发生了什么。
- @9ilsdx9rvj0lo您的观点是,即使使用虚拟的、非资源限制的计算机,我们也会发生溢出,因为最大值是在spec中定义的,我是否正确理解了?
- @Terolahtinen是的。这与计算机完全无关(除了EDCOX1中的慢度8)导致第二个选项看起来无止境),但是与Java语言规范有关,要求EDCOX1×9是一个EDCOX1×10位的二进制补码数。这给int的最大(和最小)值设置了硬界限。
- "9ILSDX9RVJ0LO"Java是"现实生活",而不是理论机器。Tero显然指的是图灵机器或你拥有的东西。
- JPMC26问题是关于Java!
- 9- ILSDX9RVJ0LO,这是Telo的观点:这是Java在一台真正的机器上运行,而不是一种理论机器,它可以无限期地增加数字。他/她正在对比OP对行为的期望,就像后者在现实世界中不可能那样。这是一个很好的观点,不应该这么轻易就被驳回。
- @JPMC26和There Tero显然是错误的,因为他的期望与现实不符
- @9ilsdx9rvj0lo:后退一步,再看看tero的评论。他非常明显地将"理论/想象"与"现实生活"(变量有限制)进行了对比,因此你攻击他关于"理论/想象"的说法是"不匹配现实"(变量有限制),然后坚持认为他是错的有点滑稽。;-)
- "DeSunStayNo",Tero似乎不完全理解Java中的int概念,他认为它是由底层硬件决定的,就像在C.一样,问题是关于Java的,因此推测其他语言的特征的评论将简单地偏离主题,如果有意识地进行,则会是拖曳。
- 9ILSDX9RVJ0LO:Java中EDOCX1 9的局限性是计算机不是"理论/虚构"的直接结果,而是受现实生活限制的限制。如果Tero指出的无限值范围是可行的,那么Java在EDCOX1的9个值中就如同C、C++一样,或者你拥有什么。即使他在拖拉,你也会喂他。现在我不再喂你了。;-)
- 是的,9ILSDX9RVJ0LO是正确的,在我的评论中我错了,Java(与其他语言不同)也有图灵类型的机器,任何其他运行的Java都会溢出。谢谢你们纠正我。
- 如果我错了,有人纠正我,但我认为值得一提的是,没有一个编译器可以检测到无限循环。我记得在算法课上有一个证明。
- @Francescodondi有,它叫javap,和jdk捆绑在一起(如果你有javac,你也有)
- @阿比里兹维,你在考虑停止的问题。
这两个例子并非无穷无尽。
问题是EDCOX1×0类型在Java中的限制(或者几乎任何其他公共语言)。当x的值达到0x7fffffff时,加上任何正值都会导致溢出,x变为负值,因此低于j。
第一个循环和第二个循环的区别在于,内部代码需要花费更多的时间,可能需要几分钟时间,直到x溢出。对于第一个示例,可能需要不到第二个,或者最可能的情况是,优化程序将删除代码,因为它没有任何效果。
正如在讨论中提到的,时间很大程度上取决于操作系统如何缓冲输出、是否输出到终端模拟器等,因此它可以比几分钟高很多。
- 我刚刚尝试了一个程序(在我的笔记本电脑上)在一个循环中打印一行。我计时了一下,它可以打印大约1000行/秒。根据n00b关于循环将执行238609294次的评论,循环终止大约需要23861秒——超过6.6小时。稍微超过"几分钟"。
- @AJB:取决于实现。iirc println()在Windows上是一个阻塞操作,而在(某些?)上是一个阻塞操作。Unix是缓冲的,所以速度快得多。也可以尝试使用print(),它会一直缓冲到碰到
(或者缓冲区填充,或者调用flush())
- 它还取决于显示输出的终端。请参阅stackoverflow.com/a/21947627/53897了解一个极端的例子(其中速度减慢是由换行引起的)
- 是的,它是在Unix上缓冲的,但它仍然是阻塞的。一旦8K左右的缓冲区填满,它就会阻塞,直到有空间为止。速度很大程度上取决于消耗速度。将输出重定向到/dev/null将是最快的,但默认情况下,将其发送到终端将需要对屏幕进行图形更新,并在显示字体减慢速度时提供更多的计算能力。
- @企鹅359:那是绝对正确的,终端机肯定会非常慢下来。关于文件-缓冲区被刷新到系统调用,并且操作系统有自己的缓冲区,这些缓冲区足够大,可以缓存全部输出,所以我不希望出现明显的减速。
- @zbynek哦,也许是这样,但这提醒了我,终端I/O通常会被线路缓冲,而不是阻塞,所以很可能每个println都会导致系统调用进一步减慢终端的速度。
由于它们被声明为int,一旦达到max值,循环将中断,因为x值将变为负数。
但是,当将system.out.println添加到循环中时,执行速度变为可见(因为输出到控制台将减慢执行速度)。但是,如果让第二个程序(循环中有syso的程序)运行足够长的时间,它应该与第一个程序(循环中没有syso的程序)具有相同的行为。
- 人们不知道向控制台发送多少垃圾邮件会降低他们的代码速度。
这可能有两个原因:
Java优化了EDCOX1×6的循环,因为在循环之后没有使用EDCOX1,1,所以简单地删除循环。您可以通过在循环后放置System.out.println(x);语句来检查这一点。
Java可能并没有真正地优化循环,而是正确地执行程序,并且最终EDCOX1〔1〕对于EDCOX1、0、和溢出将变得太大。整数溢出最有可能使整数x成为小于j的负数,因此它将从循环中出来并打印y的值。这也可以通过在循环后添加System.out.println(x);来检查。
而且,即使在第一种情况下,最终也会发生溢出,从而将其呈现为第二种情况,因此它永远不会是真正的无限循环。
- 我选择2号门。
- 真的。它以负比例前进,退出了循环。但是,一个sysout添加一个无限循环的错觉是如此缓慢。
- 1。会是个虫子。不允许编译器优化更改程序的行为。如果这是一个无限循环,那么编译器可以优化它想要的一切,但是结果必须仍然是一个无限循环。真正的解决方案是,op是错误的:两者都不是一个无限循环,一个比另一个做更多的工作,所以它需要更长的时间。
- @在这种情况下,x是一个局部变量,与任何其他变量都没有关系。因此,它可能被优化掉了。但是,我们应该查看字节码来确定是否是这种情况,不要仅仅假设编译器做了类似的事情。
这是一个有限循环,因为一旦x的值超过2,147,483,647(这是int的最大值),x将变为负值,并且不再大于j,无论是否打印y。
您只需将y的值改为100000并在循环中打印y,循环很快就会中断。
你觉得它变得无穷大的原因是System.out.println(y);使得代码的执行速度比没有任何动作要慢得多。
它们都不是无限循环,最初j=0,只要j如果你在寻找一个无限循环的例子,应该像这样
1 2 3 4 5
| int x = 6;
for (int i = 0; x < 10; i ++) {
System. out. println("Still Looping");
} |
因为(x)永远不会达到10的值;
您还可以使用双for循环创建无限循环:
1 2 3 4 5 6 7
| 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循环之后重置
有趣的问题实际上在这两种情况下,循环并不是无穷无尽的。
但它们之间的主要区别在于何时终止,以及x需要多长时间才能超过最大int值,即2,147,483,647值,然后达到溢出状态,循环终止。
理解这个问题的最好方法是测试一个简单的例子并保存它的结果。
例子:
1 2
| for(int i = 10; i > 0; i ++) {}
System. out. println("finished!"); |
输出:
1 2
| finished!
BUILD SUCCESSFUL (total time: 0 seconds) |
测试完这个无限循环后,将需要不到1秒钟的时间来终止。
1 2 3 4
| for(int i = 10; i > 0; i ++) {
System. out. println("infinite:" + i );
}
System. out. println("finished!"); |
输出:
1 2 3 4 5 6 7 8 9 10 11 12
| 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放进去测试:
1 2 3 4
| for(int i = 10; i > 0; i *=10) {
System. out. println("infinite:" + i );
}
System. out. println("finished!"); |
输出:
1 2 3 4 5 6 7 8 9
| 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的答案是正确的,同时也是这个问题的答案。