System.out.println kullanılmadığı sürece sonsuz gibi görünen döngü sona eriyor


92

Sonsuz bir döngü olması gereken basit bir kod parçam vardı çünkü xher zaman büyüyecek ve her zaman daha büyük kalacak j.

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

ancak olduğu gibi, yazdırır yve sonsuz döngü yapmaz. Nedenini anlayamıyorum. Ancak, kodu aşağıdaki şekilde ayarladığımda:

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

Sonsuz bir döngü haline geliyor ve neden olduğuna dair hiçbir fikrim yok. Java, sonsuz döngüsünü tanıyor ve ilk durumda atlıyor, ancak ikincisinde bir yöntem çağrısı yürütmek zorunda mı, böylece beklendiği gibi davranıyor mu? Şaşkın :)


4
İkinci döngü sonsuzdur çünkü üst sınır döngü değişkeninden daha hızlıx büyür . Başka bir deyişle, hiçbir zaman bir üst sınıra ulaşmayacak, dolayısıyla döngü "sonsuza kadar" çalışacaktır. Eh, sonsuza kadar değil, büyük olasılıkla bir noktada taşacaksın. jj
Tim Biegeleisen

75
Bu sonsuz bir döngü değildir, sadece ilk durumda for döngüsünden çıkmak için 238609294 kez döngü gerekir ve ikinci kez y238609294 değerini yazdırır
N00b Pr0grammer

13
tek kelimelik cevap: taşma
qwr

20
Eğlenceli bir şekilde, sonunda System.out.println(x)yerine ysorunun ne olduğunu anında
gösterecekti

9
@TeroLahtinen hayır, olmaz. İnt türünün ne olduğu konusunda şüpheleriniz varsa Java dil spesifikasyonunu okuyun. Donanımdan bağımsızdır.
9ilsdx 9rvj 0lo

Yanıtlar:


161

Her iki örnek de sonsuz değil.

Sorun, intJava'daki (veya hemen hemen diğer yaygın dillerdeki) tür sınırlamasıdır . Değeri ne xulaştığında 0x7fffffffher pozitif bir değer eklemek, taşma neden olur ve xnegatif olur, bu nedenle daha düşük j.

Birinci ve ikinci döngü arasındaki fark, iç kodun çok daha fazla zaman alması ve xtaşmalara kadar muhtemelen birkaç dakika almasıdır . İlk örnek için, saniyeden daha kısa sürebilir veya büyük olasılıkla kod, herhangi bir etkisi olmadığı için optimize edici tarafından kaldırılacaktır.

Tartışmada belirtildiği gibi, zaman büyük ölçüde işletim sisteminin çıktıyı nasıl tamponladığına, terminal öykünücüsüne çıktı verip vermediğine bağlı olacaktır, bu nedenle birkaç dakikadan çok daha uzun olabilir.


48
Bir döngüde bir satır yazdıran bir programı (dizüstü bilgisayarımda) denedim. Zamanını belirledim ve yaklaşık 1000 satır / saniye yazdırmayı başardı. N00b'nin döngünün 238609294 kez çalıştırılacağı yorumuna göre, döngünün sona ermesi yaklaşık 23861 saniye - 6,6 saatten fazla sürecektir. "Birkaç dakikadan" biraz fazla.
ajb

11
@ajb: Uygulamaya bağlıdır. println()Windows'ta IIRC bir engelleme işlemidir, oysa (bazılarında?) Unix'te arabelleğe alınır, bu nedenle çok daha hızlıdır. Ayrıca print(), \nflush()
a'ya

6
Ayrıca çıkışı gösteren terminale de bağlıdır. Uç bir örnek için stackoverflow.com/a/21947627/53897 adresine bakın (yavaşlamanın kelime kaydırmadan kaynaklandığı yer)
Thorbjørn Ravn Andersen

1
Evet, UNIX üzerinde arabelleğe alındı, ancak hala engelliyor. 8K ya da öylesine arabellek dolduğunda, yer kalana kadar bloke olacaktır. Hız, ne kadar çabuk tüketildiğine büyük ölçüde bağlı olacaktır. Çıktıyı / dev / null'a yeniden yönlendirmek en hızlısı olacaktır, ancak terminale gönderilmesi, varsayılan olarak, ekranda grafik güncellemeleri ve yazı tiplerini yavaşlatırken çok daha fazla hesaplama gücü gerektirecektir.
penguin359

2
@Zbynek oh, muhtemelen öyle, ama bu bana hatırlatıyor, I / O terminalleri tipik olarak hat tamponlanacak, bloke olmayacak, bu yüzden büyük olasılıkla her println bir sistem çağrısının terminal durumunu daha da yavaşlatmasına neden olacaktır.
penguin359

33

İnt olarak bildirildikleri için, maksimum değere ulaştığında, x değeri negatif olacağından döngü kırılacaktır.

Ancak, System.out.println döngüye eklendiğinde, yürütme hızı görünür hale gelir (konsola çıktı vermek yürütme hızını yavaşlatacağından). Bununla birlikte, 2. programın (döngü içinde syso bulunan program) yeterince uzun süre çalışmasına izin verirseniz, ilk programla (döngü içinde syso olmayan program) aynı davranışa sahip olmalıdır.


21
İnsanlar konsola ne kadar spam göndermenin kodlarını yavaşlatabileceğinin farkında değiller.
user9993

13

Bunun iki nedeni olabilir:

  1. Java, fordöngüyü optimize eder ve döngüden xsonra kullanılmadığı için, yalnızca döngüyü kaldırır. System.out.println(x);Döngünün arkasına ifade koyarak bunu kontrol edebilirsiniz .

  2. Java'nın döngüyü gerçekten optimize etmemesi ve programı doğru bir şekilde çalıştırması ve sonunda xçok fazla büyüyüp inttaşması mümkün olabilir. Tamsayı taşması büyük olasılıkla tamsayıyı xnegatif yapacak ve j'den küçük olacak ve böylece döngüden çıkacak ve değerini yazdıracaktır y. Bu System.out.println(x);, döngüden sonra eklenerek de kontrol edilebilir .

Ayrıca, ilk durumda bile eninde sonunda taşma gerçekleşecek ve böylece onu ikinci duruma getirecek, böylece asla gerçek sonsuz bir döngü olmayacak.


14
2 numaralı kapıyı seçiyorum.
Robby Cornelissen

Doğru. Negatif ölçekte gitti ve döngüden çıktı. Ancak a sysout, sonsuz bir döngü illüzyonu eklemek için çok yavaştır.
Pavan Kumar

4
1. Hata olur. Derleyici optimizasyonlarının bir programın davranışını değiştirmesine izin verilmez. Bu sonsuz bir döngüse, derleyici istediği her şeyi optimize edebilir, ancak sonuç yine de sonsuz döngü olmalıdır. Gerçek çözüm, OP'nin hatalı olmasıdır: İkisi de sonsuz döngü değildir, biri diğerinden daha fazla iş yapar, bu yüzden daha uzun sürer.
Jörg W Mittag

1
@ JörgWMittag Bu durumda x, başka hiçbir şeyle ilişkisi olmayan yerel bir değişkendir. Bu nedenle, optimize edilmiş olması mümkündür. Ancak durumun böyle olup olmadığını belirlemek için bayt koduna bakılmalıdır, asla derleyicinin böyle bir şey yaptığını varsaymayın.
HopefullyHelpful

1

Her ikisi de sonsuz döngü değildir, başlangıçta j = 0, j <x, j arttığı (j ++) ve j bir tamsayıdır, bu nedenle döngü maksimum değere ulaşana kadar çalışır ve sonra taşar (Tamsayı Taşması koşuldur Bu, çarpma veya toplama gibi bir aritmetik işlemin sonucu, onu depolamak için kullanılan tamsayı türünün maksimum boyutunu aştığında oluşur.). ikinci örnek için sistem, döngü kesilene kadar y'nin değerini yazdırır.

sonsuz bir döngü örneği arıyorsanız, şöyle görünmelidir

int x = 6;

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

çünkü (x) asla 10 değerine ulaşmaz;

çift ​​for döngüsü ile sonsuz bir döngü de oluşturabilirsiniz:

int i ;

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

bu döngü sonsuzdur çünkü birinci for döngüsü i <10 der ki bu doğrudur, bu yüzden ikinci for döngüsüne gider ve ikinci for döngüsü (i) değerini == 5 olana kadar artırır. for döngüsü tekrar i <10 olduğundan, işlem kendini tekrar etmeye devam eder çünkü ikinci for döngüsünden sonra sıfırlanır


1

Bu sonlu bir döngüdür, çünkü bir kez ( xa'nın 2,147,483,647maksimum değeri olan int) aşıldığında , xnegatif olur ve jy yazdırsanız da basmasanız da hiç birinden büyük olmaz.

Döngüdeki yto 100000ve print değerini değiştirebilir yve döngü çok yakında kırılır.

Bunun sonsuz olduğunu hissetmenizin nedeni System.out.println(y);, kodun herhangi bir işlem yapılmamasından çok daha yavaş yürütülmesi.


0

İlginç bir problem Aslında her iki durumda da döngü sonsuz değil

Ancak aralarındaki temel fark, ne zaman sona ereceği ve xmaksimum intdeğeri aşmak için ne kadar zaman alacağıdır , 2,147,483,647bundan sonra taşma durumuna ulaşacak ve döngü sona erecektir.

Bu sorunu anlamanın en iyi yolu, basit bir örneği test etmek ve sonuçlarını korumaktır.

Örnek :

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

Çıktı:

finished!
BUILD SUCCESSFUL (total time: 0 seconds)

Bu sonsuz döngüyü test ettikten sonra sonlandırılması 1 saniyeden az sürecektir.

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

Çıktı:

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

Bu test senaryosunda, programı sonlandırmak ve bitirmek için geçen sürede büyük bir fark göreceksiniz.

Sabırlı değilseniz, bu döngünün sonsuz olduğunu ve sona ermeyeceğini düşüneceksiniz, ancak aslında sona erdirmek ve taşma durumuna ideğer olarak ulaşmak saatler alacaktır .

Son olarak, for döngüsünün içine print deyimini koyduktan sonra, print deyimi olmayan ilk durumda döngüden çok daha fazla zaman alacağını anladık.

Programı çalıştırmak için geçen süre bilgisayarınızın özelliklerine, özellikle işlem gücüne (işlemci kapasitesi), işletim sistemine ve programı derleyen IDE'nize bağlıdır.

Bu durumu test ediyorum:

Lenovo 2.7 GHz Intel Core i5

İşletim Sistemi: Windows 8.1 64x

IDE: NetBeans 8.2

Programı bitirmek yaklaşık 8 saat (486 dakika) sürer.

Ayrıca, for döngüsündeki adım artışının i = i + 1maks. İnt değerine ulaşmak için çok yavaş olduğunu fark edebilirsiniz .

Döngüyü daha kısa sürede test etmek için bu faktörü değiştirebilir ve adım artışını daha hızlı yapabiliriz.

koyup i = i * 10test edersek :

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

Çıktı:

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

Gördüğünüz gibi önceki döngüye göre çok hızlı

programı çalıştırmayı sonlandırmak ve bitirmek 1 saniyeden az sürer.

Bu test örneğinden sonra, Zbynek Vyskovsky'nin soruna açıklık getirmesi ve geçerliliğini kanıtlaması gerektiğini düşünüyorum - kvr000'in cevabı , bu soruya da cevap olacaktır .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.