Die scheinbar endlose Schleife wird beendet, sofern nicht System.out.println verwendet wird

91

Ich hatte ein einfaches Stück Code, das eigentlich eine Endlosschleife sein sollte, da xes immer größer wird und immer größer bleibt als j.

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

aber so wie es ist, wird es gedruckt yund nicht endlos wiederholt. Ich kann nicht herausfinden warum. Wenn ich den Code jedoch folgendermaßen anpasse:

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

Es wird eine Endlosschleife und ich habe keine Ahnung warum. Erkennt Java eine Endlosschleife und überspringt sie in der ersten Situation, muss aber in der zweiten einen Methodenaufruf ausführen, damit sie sich wie erwartet verhält? Verwirrt :)

Omar
quelle
4
Die zweite Schleife ist endlos, da die Obergrenze schnellerx wächst als die Schleifenvariable . Mit anderen Worten, wird niemals eine Obergrenze erreichen, daher wird die Schleife "für immer" laufen. Nun, nicht für immer, Sie werden höchstwahrscheinlich irgendwann einen Überlauf bekommen. jj
Tim Biegeleisen
75
Es ist keine Endlosschleife, es dauert nur 238609294 Mal, bis die Schleife im ersten Fall aus der for-Schleife herauskommt, und beim zweiten Mal wird der Wert y238609294 Mal
gedruckt
13
Ein-Wort-Antwort: Überlauf
qwr
20
Amüsanterweise hätte System.out.println(x)statt yam Ende sofort gezeigt, was das Problem war
JollyJoker
9
@TeroLahtinen nein, würde es nicht. Lesen Sie die Java-Sprachspezifikation, wenn Sie Zweifel haben, was der Typ int ist. Es ist hardwareunabhängig.
9ilsdx 9rvj 0lo

Antworten:

161

Beide Beispiele sind nicht endlos.

Das Problem ist die Einschränkung des intTyps in Java (oder so ziemlich jeder anderen gängigen Sprache). Wenn der Wert von xerreicht 0x7fffffff, führt das Hinzufügen eines positiven Werts zu einem Überlauf und der Wert xwird negativ, daher niedriger als j.

Der Unterschied zwischen der ersten und der zweiten Schleife besteht darin, dass der innere Code viel mehr Zeit benötigt und es wahrscheinlich einige Minuten dauern würde, bis er xüberläuft. Im ersten Beispiel kann es weniger als eine Sekunde dauern, oder der Code wird höchstwahrscheinlich vom Optimierer entfernt, da er keine Auswirkungen hat.

Wie in der Diskussion erwähnt, hängt die Zeit stark davon ab, wie das Betriebssystem die Ausgabe puffert, ob sie an den Terminalemulator usw. ausgegeben wird, sodass sie viel höher als einige Minuten sein kann.

Zbynek Vyskovsky - kvr000
quelle
48
Ich habe gerade ein Programm (auf meinem Laptop) ausprobiert, das eine Zeile in einer Schleife druckt. Ich habe es zeitlich festgelegt und es konnte ungefähr 1000 Zeilen / Sekunde drucken. Basierend auf dem Kommentar von N00b, dass die Schleife 238609294 Mal ausgeführt wird, dauert es ungefähr 23861 Sekunden, bis die Schleife beendet ist - über 6,6 Stunden. Ein bisschen mehr als "einige Minuten".
Ajb
11
@ajb: Kommt auf die Implementierung an. IIRC println()unter Windows ist eine Blockierungsoperation, während es unter (einigen?) Unix gepuffert ist und daher viel schneller geht. Versuchen Sie auch zu verwenden print(), welche Puffer, bis es ein trifft \n(oder der Puffer füllt oder flush()heißt)
BlueRaja - Danny Pflughoeft
6
Es hängt auch vom Terminal ab, das den Ausgang anzeigt. Siehe stackoverflow.com/a/21947627/53897 für ein extremes Beispiel (wo die Verlangsamung auf Zeilenumbrüche zurückzuführen war)
Thorbjørn Ravn Andersen
1
Ja, es ist unter UNIX gepuffert, aber es blockiert immer noch. Sobald der 8K-Puffer voll ist, wird er blockiert, bis Platz vorhanden ist. Die Geschwindigkeit hängt stark davon ab, wie schnell sie verbraucht wird. Das Umleiten der Ausgabe nach / dev / null ist am schnellsten, aber wenn sie standardmäßig an das Terminal gesendet wird, sind Grafikaktualisierungen auf dem Bildschirm und viel mehr Rechenleistung erforderlich, da die Schriftarten langsamer werden.
Pinguin359
2
@Zbynek oh, wahrscheinlich, aber das erinnert mich daran, dass die E / A der Terminals normalerweise zeilengepuffert und nicht blockiert werden, so dass höchstwahrscheinlich jeder Druck zu einem Systemaufruf führt, der den Terminalfall weiter verlangsamt.
Pinguin359
33

Da sie als int deklariert sind, wird die Schleife unterbrochen, sobald der Maximalwert erreicht ist, da der x-Wert negativ wird.

Wenn jedoch System.out.println zur Schleife hinzugefügt wird, wird die Ausführungsgeschwindigkeit sichtbar (da die Ausgabe an die Konsole die Ausführungsgeschwindigkeit verlangsamt). Wenn Sie jedoch das zweite Programm (das mit syso in der Schleife) lange genug laufen lassen, sollte es das gleiche Verhalten wie das erste haben (das ohne syso in der Schleife).

Ace Zachary
quelle
21
Die Leute wissen nicht, wie viel Spam auf die Konsole ihren Code verlangsamen kann.
user9993
13

Dafür kann es zwei Gründe geben:

  1. Java optimiert die forSchleife und xentfernt die Schleife einfach, da sie nach der Schleife nicht verwendet wird. Sie können dies überprüfen, indem Sie eine System.out.println(x);Anweisung nach der Schleife setzen.

  2. Es ist möglich, dass Java die Schleife nicht wirklich optimiert und das Programm korrekt ausführt und schließlich xzu groß wird intund überläuft. Ein Ganzzahlüberlauf macht die Ganzzahl höchstwahrscheinlich xnegativ, was kleiner als j ist, und kommt daher aus der Schleife heraus und gibt den Wert von aus y. Dies kann auch durch Hinzufügen System.out.println(x);nach der Schleife überprüft werden .

Selbst im ersten Fall tritt schließlich ein Überlauf auf, wodurch der zweite Fall erreicht wird, sodass es sich nie um eine echte Endlosschleife handelt.

Ashok Vishnoi
quelle
14
Ich wähle Tür Nummer 2.
Robby Cornelissen
Wahr. Es ging auf die negative Skala und verließ die Schleife. Aber a sysoutist so langsam, um die Illusion einer Endlosschleife hinzuzufügen.
Pavan Kumar
4
1. Wäre ein Fehler. Compiler-Optimierungen dürfen das Verhalten eines Programms nicht ändern. Wenn dies eine Endlosschleife war, kann der Compiler alles optimieren, was er will. Das Ergebnis muss jedoch immer noch eine Endlosschleife sein. Die wirkliche Lösung ist, dass das OP falsch ist: Keine der beiden ist eine Endlosschleife, eine macht einfach mehr Arbeit als die andere, also dauert es länger.
Jörg W Mittag
1
@ JörgWMittag In diesem Fall ist x eine lokale Variable ohne Beziehung zu irgendetwas anderem. Als solches ist es möglich, dass es weg optimiert wird. Aber man sollte sich den Bytecode ansehen, um festzustellen, ob dies der Fall ist, und niemals einfach davon ausgehen, dass der Compiler so etwas getan hat.
Hoffentlich
1

Sie sind beide keine Endlosschleifen, anfänglich j = 0, solange j <x, j zunimmt (j ++) und j eine ganze Zahl ist, so dass die Schleife laufen würde, bis sie den Maximalwert erreicht und dann überläuft (ein ganzzahliger Überlauf ist die Bedingung Dies tritt auf, wenn das Ergebnis einer arithmetischen Operation wie Multiplikation oder Addition die maximale Größe des zum Speichern verwendeten Ganzzahltyps überschreitet.). Im zweiten Beispiel gibt das System nur den Wert von y aus, bis die Schleife unterbrochen wird.

Wenn Sie nach einem Beispiel für eine Endlosschleife suchen, sollte es so aussehen

int x = 6;

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

weil (x) niemals den Wert 10 erreichen würde;

Sie können auch eine Endlosschleife mit einer doppelten for-Schleife erstellen:

int i ;

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

Diese Schleife ist unendlich, weil die erste for-Schleife i <10 sagt, was wahr ist, so dass sie in die zweite for-Schleife geht und die zweite for-Schleife den Wert von (i) erhöht, bis sie == 5 ist. Dann geht sie in die erste über for-Schleife erneut, da i <10 ist, wiederholt sich der Prozess immer wieder, da er nach der zweiten for-Schleife zurückgesetzt wird

Kennedy
quelle
1

Es ist eine endliche Schleife, denn sobald der Wert von xüberschreitet 2,147,483,647(was der Maximalwert von a ist int), xwird er negativ und nicht größer als jjeder andere, unabhängig davon, ob Sie y drucken oder nicht.

Sie können einfach den Wert von yto ändern 100000und yin der Schleife drucken , und die Schleife wird sehr bald unterbrochen.

Der Grund, warum Sie das Gefühl haben, dass es unendlich wird, ist, dass der System.out.println(y);Code sehr viel langsamer ausgeführt wird als ohne Aktionen.

Joe Cheng
quelle
0

Interessantes Problem In beiden Fällen ist die Schleife nicht endlos

Der Hauptunterschied zwischen ihnen besteht jedoch darin, wann sie beendet werden und wie viel Zeit benötigt xwird, um den intMaximalwert zu überschreiten. Danach 2,147,483,647erreicht sie den Überlaufzustand und die Schleife endet.

Der beste Weg, um dieses Problem zu verstehen, besteht darin, ein einfaches Beispiel zu testen und die Ergebnisse beizubehalten.

Beispiel :

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

Ausgabe:

finished!
BUILD SUCCESSFUL (total time: 0 seconds)

Nach dem Testen dieser Endlosschleife dauert das Beenden weniger als 1 Sekunde.

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

Ausgabe:

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

In diesem Testfall werden Sie einen großen Unterschied in der Zeit feststellen, die zum Beenden und Beenden des Programms benötigt wird.

Wenn Sie nicht geduldig sind, werden Sie denken, dass diese Schleife endlos ist und nicht beendet wird, aber tatsächlich wird es Stunden dauern, bis sie beendet ist und den Überlaufzustand bei iWert erreicht.

Nachdem wir die print-Anweisung in die for-Schleife eingefügt haben, haben wir schließlich festgestellt, dass es im ersten Fall ohne print-Anweisung viel länger dauert als die Schleife.

Die zum Ausführen des Programms benötigte Zeit hängt von Ihren Computerspezifikationen ab, insbesondere von der Verarbeitungsleistung (Prozessorkapazität), dem Betriebssystem und Ihrer IDE, die das Programm kompiliert.

Ich teste diesen Fall auf:

Lenovo 2,7 GHz Intel Core i5

Betriebssystem: Windows 8.1 64x

IDE: NetBeans 8.2

Das Beenden des Programms dauert ca. 8 Stunden (486 Minuten).

Sie können auch feststellen, dass das Schrittinkrement in der for-Schleife i = i + 1ein sehr langsamer Faktor ist, um den maximalen int-Wert zu erreichen.

Wir können diesen Faktor ändern und das Schrittinkrement beschleunigen, um die Schleife in kürzerer Zeit zu testen.

wenn wir es setzen i = i * 10und testen:

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

Ausgabe:

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

Wie Sie sehen, ist es im Vergleich zur vorherigen Schleife sehr schnell

Das Beenden und Beenden des Programms dauert weniger als 1 Sekunde.

Nach diesem Testbeispiel sollte es meines Erachtens das Problem klären und die Gültigkeit der Antwort von Zbynek Vyskovsky - kvr000 beweisen . Auch diese Frage wird beantwortet .

Oghli
quelle