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は無限ループを認識し、最初の状況ではそれをスキップしますが、2番目の状況でメソッド呼び出しを実行して期待どおりに動作するようにする必要がありますか?混乱しています:)


4
上限がループ変数より速くx増加するため、2番目のループは無限です。つまり、上限に達することはないため、ループは「永久に」実行されます。まあ、永遠ではありませんが、ある時点でおそらくオーバーフローが発生します。jj
Tim Biegeleisen 2017

75
これは無限ループではなく、最初のケースでforループから抜けるのに238609294回かかり、2回目にy238609294回の値を出力するだけです
N00b Pr0grammer

13
1つの単語の答え:オーバーフロー
qwr

20
面白いことに、最後System.out.println(x)yではなく、問題が何であったかを即座に示したでしょう
JollyJoker

9
@TeroLahtinenいいえ、そうではありません。int型が何か疑問がある場合は、Java言語仕様を読んでください。ハードウェアに依存しません。
9ilsdx 9rvj 0lo

回答:


161

どちらの例も無限ではありません。

問題は、intJava(または他のほとんどの一般的な言語)の型の制限です。の値がにx達すると0x7fffffff、正の値を追加するとオーバーフローが発生し、xが負になるため、よりも低くなりjます。

最初のループと2番目のループの違いは、内部コードの方がはるかに時間がかかり、xオーバーフローするまでに数分かかることです。最初の例の場合、2秒未満かかる場合があります。または、効果がないため、コードはオプティマイザによって削除されます。

説明で述べたように、時間はOSが出力をバッファリングする方法、ターミナルエミュレータに出力するかどうかなどに大きく依存するため、数分よりもはるかに長くなる可能性があります。


48
ループで行を出力するプログラム(ラップトップで)を試しました。計時し、毎秒約1000行を印刷することができました。ループは238609294回実行されるというN00bのコメントに基づくと、ループが終了するのに約23861秒、6.6時間以上かかります。「数分」以上の少し。
ajb 2017

11
@ajb:実装によって異なります。println()WindowsのIIRC はブロッキング操作ですが、(一部の)Unixではバッファリングされているため、はるかに高速になります。また、使用してみてくださいprint()、それがヒットするまでどのバッファ、\n(またはバッファ塗りつぶしを、またはflush()と呼ばれる)
BlueRaja -ダニーPflughoeft

6
また、出力を表示する端末によっても異なります。極端な例については、stackoverflow.com / a / 21947627/53897を参照してください(速度の低下は単語の折り返しが原因でした)
–ThorbjørnRavn Andersen 2017

1
はい、UNIXではバッファリングされますが、それでもブロックされます。8K程度のバッファがいっぱいになると、空き領域ができるまでブロックされます。速度は、それが消費される速度に大きく依存します。出力を/ dev / nullにリダイレクトするのが最も高速ですが、デフォルトでターミナルに送信すると、フォントのレンダリングが遅くなるため、画面へのグラフィックの更新とはるかに多くの計算能力が必要になります。
ペンギン359

2
@Zbynekああ、そうかもしれませんが、それを思い出してください。端末のI / Oは通常、ブロックされずにラインバッファリングされるので、すべてのprintlnがシステムコールを引き起こし、端末のケースがさらに遅くなります。
ペンギン359

33

これらはintとして宣言されているため、最大値に達すると、xの値が負になるためループが中断します。

ただし、System.out.printlnがループに追加されると、実行速度が表示されます(コンソールに出力すると実行速度が低下するため)。ただし、2番目のプログラム(ループ内にsysoがあるプログラム)を十分長く実行させると、最初のプログラム(ループ内にsysoがないプログラム)と同じ動作になります。


21
人々は、コンソールへの大量のスパムがコードの速度を低下させる可能性があることを理解していません。
user9993 2017

13

これには2つの理由が考えられます。

  1. Javaはforループを最適化し、ループのx後は使用しないため、単にループを削除します。これSystem.out.println(x);は、ループの後にステートメントを置くことで確認できます。

  2. Javaが実際にループを最適化しておらず、プログラムを正しく実行していて、最終的にxは大きくなりすぎてintオーバーフローする可能性があります。整数オーバーフローはおそらくxjより小さい整数を負にするため、ループから出ての値を出力しyます。これSystem.out.println(x);は、ループの後に追加することでも確認できます。

また、最初のケースでも最終的にオーバーフローが発生し、2番目のケースにレンダリングされるため、真のエンドレスループになることはありません。


14
私はドア番号2を選択します
Robby Cornelissen

そうだね。それは負のスケールで行き、ループを抜けました。しかし、sysout無限ループのような錯覚を加えるのはとても遅いです。
Pavan Kumar 2017

4
1.バグになります。コンパイラの最適化では、プログラムの動作を変更できません。これが無限ループである場合、コンパイラーは必要なすべてを最適化しますが、結果は依然として無限ループでなければなりません。本当の解決策は、OPが間違っていることです。2つはどちらも無限ループではなく、一方が他方より多くの作業を行うだけなので、時間がかかります。
イェルクWミッターク

1
@JörgWMittagこの場合、xは他とは何の関係もないローカル変数です。そのため、最適化されてしまう可能性があります。しかし、バイトコードを見てそれが事実であるかどうかを判断する必要があります。コンパイラがそのようなことをしたと仮定しないでください。
2017

1

これらはどちらも無限ループではありません。j<xである限り、最初はj = 0であり、jは増加し(j ++)、jは整数であるため、ループは最大値に到達するまで実行され、オーバーフローします(整数オーバーフローが条件です)これは、乗算や加算などの算術演算の結果が、それを格納するために使用される整数型の最大サイズを超えたときに発生します。2番目の例の場合、システムはループが中断するまで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であり、真であるので2番目のforループに入り、2番目のforループは(i)の値を== 5になるまで増やします。その後、最初のforループに進みますi <10であるため、forループが再び発生します。2番目のforループの後でリセットされるため、プロセスは繰り返し繰り返されます。


1

の値x2,147,483,647(の最大値int)を超えると、yを出力するかどうかに関係xなく、負になり、それ以下になるため、これは有限ループjです。

あなただけの値に変更することができますyにし100000て印刷yループ内で、ループは非常にすぐに解除されます。

無限になったと感じる理由は、がSystem.out.println(y);アクションを実行しない場合よりもコードの実行が非常に遅くなるためです。


0

興味深い問題実際にはどちらの場合もループは無限ではありません

しかし、それらの間の主な違いは、いつ終了するかと、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)

このテストケースでは、プログラムの実行を終了して終了するのにかかる時間に大きな違いがあることに気づくでしょう。

忍耐力がないと、このループは無限であり、終了しないと思いますが、実際には、終了してivalueでオーバーフロー状態になるまでに数時間かかります。

最後に、printステートメントをforループ内に置いた後、printステートメントのない最初のケースのループよりもはるかに時間がかかると結論しました。

プログラムの実行にかかる時間は、特定の処理能力(プロセッサ容量)、オペレーティングシステム、およびプログラムをコンパイルしているIDEのコンピュータ仕様によって異なります。

私はこのケースをテストします:

Lenovo 2.7 GHz Intel Core i5

OS: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の回答の妥当性を証明する必要があると思います。また、この質問に対する回答になります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.