การวนซ้ำที่ไม่มีที่สิ้นสุดดูเหมือนจะสิ้นสุดลงเว้นแต่จะใช้ 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 รับรู้การวนซ้ำไม่รู้จบและข้ามไปในสถานการณ์แรก แต่ต้องเรียกใช้เมธอดในวินาทีเพื่อให้มันทำงานตามที่คาดไว้หรือไม่? งง :)


4
วงที่สองเป็นที่สิ้นสุดเพราะขอบเขตบนxเติบโตได้เร็วขึ้นjกว่าตัวแปร loop กล่าวอีกนัยหนึ่งคือjจะไม่ชนขอบเขตบนดังนั้นลูปจะทำงาน "ตลอดไป" ไม่ใช่ตลอดไปคุณจะได้รับน้ำล้นในบางจุดที่เป็นไปได้มากที่สุด
Tim Biegeleisen

75
ไม่ใช่การวนซ้ำที่ไม่มีที่สิ้นสุดเพียงแค่ใช้เวลา 238609294 ครั้งในการวนซ้ำเพื่อออกจากลูป for ในกรณีแรกและครั้งที่สองจะพิมพ์ค่าy238609294 ครั้ง
N00b Pr0grammer

13
คำตอบคำเดียว: ล้น
qwr

20
น่าขบขันSystem.out.println(x)แทนที่จะyในตอนท้ายจะแสดงให้เห็นทันทีว่าปัญหาคืออะไร
JollyJoker

9
@TeroLahtinen ไม่มันจะไม่ อ่านข้อกำหนดภาษา Java หากคุณมีข้อสงสัยว่าประเภท int คืออะไร เป็นฮาร์ดแวร์ที่เป็นอิสระ
9ilsdx 9rvj 0lo

คำตอบ:


161

ทั้งสองตัวอย่างไม่สิ้นสุด

ปัญหาคือข้อ จำกัด ของintประเภทใน Java (หรือภาษาทั่วไปอื่น ๆ ) เมื่อค่าของxต้นน้ำ0x7fffffffเพิ่มมูลค่าในเชิงบวกใด ๆ ที่จะส่งผลให้เกิดน้ำล้นและกลายเป็นลบจึงต่ำกว่าxj

ความแตกต่างระหว่างลูปแรกและครั้งที่สองคือโค้ดด้านในจะใช้เวลามากกว่ามากและอาจใช้เวลาหลายนาทีกว่าจะxล้น สำหรับตัวอย่างแรกอาจใช้เวลาน้อยกว่าวินาทีหรือส่วนใหญ่อาจเป็นไปได้ว่าโค้ดจะถูกลบออกโดยเครื่องมือเพิ่มประสิทธิภาพเนื่องจากไม่มีผลใด ๆ

ดังที่ได้กล่าวไว้ในการสนทนาเวลาจะขึ้นอยู่กับว่า OS บัฟเฟอร์เอาต์พุตอย่างไรไม่ว่าจะส่งออกไปยังโปรแกรมจำลองเทอร์มินัล ฯลฯ ดังนั้นจึงอาจสูงกว่าไม่กี่นาที


48
ฉันเพิ่งลองใช้โปรแกรม (บนแล็ปท็อป) ที่พิมพ์เส้นเป็นวง ฉันตั้งเวลาไว้และพิมพ์ได้ประมาณ 1,000 บรรทัด / วินาที จากความคิดเห็นของ N00b ที่ว่าลูปจะดำเนินการ 238609294 ครั้งจะใช้เวลาประมาณ 23861 วินาทีในการสิ้นสุดลูป - มากกว่า 6.6 ชั่วโมง มากกว่า "หลายนาที"
ajb

11
@ajb: ขึ้นอยู่กับการนำไปใช้งาน IIRC println()บน Windows เป็นการดำเนินการปิดกั้นในขณะที่ Unix (บาง?) บัฟเฟอร์จะเร็วขึ้นมาก ลองใช้print()ด้วยซึ่งบัฟเฟอร์จนกว่าจะถึงจุด\n(หรือบัฟเฟอร์เติมหรือflush()เรียกว่า)
BlueRaja - Danny Pflughoeft

6
นอกจากนี้ยังขึ้นอยู่กับเทอร์มินัลที่แสดงเอาต์พุต ดูstackoverflow.com/a/21947627/53897สำหรับตัวอย่างที่รุนแรง (ซึ่งการชะลอตัวเกิดจากการตัดคำ)
Thorbjørn Ravn Andersen

1
ใช่บัฟเฟอร์บน UNIX แต่ยังคงบล็อกอยู่ เมื่อเติมบัฟเฟอร์ 8K หรือมากกว่านั้นมันจะปิดกั้นจนกว่าจะมีที่ว่าง ความเร็วจะขึ้นอยู่กับความเร็วในการบริโภค การเปลี่ยนเส้นทางเอาต์พุตเป็น / dev / null จะเร็วที่สุด แต่การส่งไปยังเทอร์มินัลซึ่งเป็นค่าเริ่มต้นจะต้องมีการอัปเดตกราฟิกไปยังหน้าจอและพลังในการประมวลผลที่มากขึ้นเนื่องจากการแสดงผลแบบอักษรทำให้ช้าลง
penguin359

2
@Zbynek โอ้อาจจะเป็นเช่นนั้น แต่ที่เตือนฉันว่าเทอร์มินัล I / O มักจะเป็นไลน์บัฟเฟอร์ไม่ใช่บล็อกดังนั้นส่วนใหญ่แล้วการพิมพ์ทุกครั้งจะส่งผลให้การเรียกระบบช้าลง
penguin359

33

เนื่องจากมีการประกาศเป็น int เมื่อถึงค่าสูงสุดลูปจะแตกเนื่องจากค่า x จะกลายเป็นลบ

แต่เมื่อเพิ่ม System.out.println ลงในลูปความเร็วของการดำเนินการจะปรากฏขึ้น (เนื่องจากการส่งออกไปยังคอนโซลจะทำให้ความเร็วในการดำเนินการช้าลง) อย่างไรก็ตามหากคุณปล่อยให้โปรแกรมที่ 2 (โปรแกรมที่มี syso อยู่ในลูป) ทำงานนานพอก็ควรมีลักษณะการทำงานเหมือนกับโปรแกรมแรก (โปรแกรมที่ไม่มี syso อยู่ในลูป)


21
ผู้คนไม่ทราบว่าการส่งสแปมไปยังคอนโซลสามารถทำให้โค้ดช้าลงได้มากเพียงใด
user9993

13

อาจมีสาเหตุสองประการสำหรับสิ่งนี้:

  1. Java ปรับforลูปให้เหมาะสมที่สุดและเนื่องจากไม่มีการใช้xหลังลูปเพียงแค่ลบลูปออก คุณสามารถตรวจสอบได้โดยใส่System.out.println(x);คำสั่งหลังลูป

  2. อาจเป็นไปได้ว่า Java ไม่ได้ปรับลูปให้เหมาะสมจริง ๆ และกำลังดำเนินการโปรแกรมอย่างถูกต้องและในที่สุดก็xจะใหญ่เกินไปสำหรับintและล้น จำนวนเต็มล้นส่วนใหญ่อาจจะทำให้จำนวนเต็มเป็นเชิงลบซึ่งจะมีขนาดเล็กกว่าเจและดังนั้นจึงจะออกมาจากวงและพิมพ์ค่าของx yนอกจากนี้ยังสามารถตรวจสอบได้โดยเพิ่มSystem.out.println(x);หลังลูป

นอกจากนี้แม้ในกรณีแรกจะเกิดการล้นในที่สุดดังนั้นการแสดงผลเป็นกรณีที่สองดังนั้นมันจะไม่วนซ้ำไม่รู้จบอย่างแท้จริง


14
ฉันเลือกประตูหมายเลข 2
Robby Cornelissen

จริง. มันไปในระดับลบและออกจากลูป แต่ a sysoutช้ามากที่จะเพิ่มภาพลวงตาของวงวนที่ไม่มีที่สิ้นสุด
Pavan Kumar

4
1. จะเป็นจุดบกพร่อง การปรับแต่งคอมไพลเลอร์ไม่ได้รับอนุญาตให้เปลี่ยนลักษณะการทำงานของโปรแกรม หากนี่เป็นลูปที่ไม่มีที่สิ้นสุดคอมไพเลอร์อาจปรับให้เหมาะสมทั้งหมดที่ต้องการอย่างไรก็ตามผลลัพธ์ยังคงเป็นลูปที่ไม่มีที่สิ้นสุด วิธีแก้ปัญหาที่แท้จริงคือ OP ผิดพลาด: ทั้งสองไม่ได้เป็นลูปที่ไม่มีที่สิ้นสุดอันหนึ่งทำงานได้มากกว่าอีกอันจึงใช้เวลานานกว่า
Jörg W Mittag

1
@ JörgWMittagในกรณีนี้ x คือตัวแปรท้องถิ่นที่ไม่มีความสัมพันธ์กับสิ่งอื่นใด ด้วยเหตุนี้จึงเป็นไปได้ที่จะปรับให้เหมาะสมที่สุด แต่เราควรดูที่ bytecode เพื่อพิจารณาว่าเป็นเช่นนั้นหรือไม่อย่าเพิ่งคิดว่าคอมไพเลอร์ทำอะไรแบบนั้น
หวังว่าจะเป็นประโยชน์

1

ทั้งคู่ไม่ใช่ลูปที่ไม่มีที่สิ้นสุดเริ่มต้น j = 0 ตราบใดที่ j <x, j เพิ่มขึ้น (j ++) และ j เป็นจำนวนเต็มดังนั้นการวนซ้ำจะทำงานจนกว่าจะถึงค่าสูงสุดจากนั้นจึงล้น (An Integer Overflow เป็นเงื่อนไข ที่เกิดขึ้นเมื่อผลลัพธ์ของการคำนวณทางคณิตศาสตร์เช่นการคูณหรือการบวกเกินขนาดสูงสุดของชนิดจำนวนเต็มที่ใช้ในการจัดเก็บ) สำหรับตัวอย่างที่สองระบบจะพิมพ์ค่า y จนกว่าลูปจะแตก

หากคุณกำลังมองหาตัวอย่างของการวนซ้ำที่ไม่สิ้นสุดควรมีลักษณะดังนี้

int x = 6;

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

เพราะ (x) จะไม่ได้รับค่า 10;

คุณยังสามารถสร้างลูปที่ไม่มีที่สิ้นสุดโดยมีคู่สำหรับลูป:

int i ;

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

ลูปนี้ไม่มีที่สิ้นสุดเพราะอันแรกสำหรับลูปบอกว่า i <10 ซึ่งเป็นจริงดังนั้นมันจึงเข้าสู่วินาทีสำหรับลูปและครั้งที่สองสำหรับลูปจะเพิ่มค่าของ (i) จนกว่าจะเป็น == 5 จากนั้นจะเข้าสู่อันแรก สำหรับการวนซ้ำอีกครั้งเนื่องจาก i <10 กระบวนการจะทำซ้ำตัวเองต่อไปเพราะจะรีเซ็ตหลังจากที่สองสำหรับลูป


1

มันเป็นวง จำกัด เพราะเมื่อค่าxเกิน2,147,483,647(ซึ่งเป็นค่าสูงสุดของ an int) xจะกลายเป็นลบและไม่มากกว่าjใด ๆ ไม่ว่าคุณจะพิมพ์ y หรือไม่ก็ตาม

คุณสามารถเปลี่ยนค่าของ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)

ในกรณีทดสอบนี้คุณจะสังเกตเห็นความแตกต่างอย่างมากในเวลาที่ใช้ในการยุติและรันโปรแกรมให้เสร็จสิ้น

หากคุณไม่อดทนคุณจะคิดว่าการวนซ้ำนี้ไม่มีที่สิ้นสุดและจะไม่สิ้นสุด แต่ในความเป็นจริงจะใช้เวลาหลายชั่วโมงในการยุติและเข้าถึงสถานะล้นiตามมูลค่า

ในที่สุดเราก็ได้ข้อสรุปหลังจากที่เราใส่คำสั่งพิมพ์ภายในสำหรับการวนซ้ำว่าจะใช้เวลามากกว่าการวนซ้ำในกรณีแรกที่ไม่มีคำสั่งพิมพ์

เวลาที่ใช้ในการรันโปรแกรมนั้นขึ้นอยู่กับข้อกำหนดของคอมพิวเตอร์ของคุณในด้านพลังการประมวลผลโดยเฉพาะ (ความจุของโปรเซสเซอร์) ระบบปฏิบัติการและ IDE ของคุณซึ่งกำลังรวบรวมโปรแกรม

ฉันทดสอบกรณีนี้เมื่อ:

เลอโนโว 2.7 GHz Intel Core i5

ระบบปฏิบัติการ: Windows 8.1 64x

IDE: NetBeans 8.2.2

ใช้เวลาประมาณ 8 ชั่วโมง (486 นาที) เพื่อจบรายการ

นอกจากนี้คุณสามารถสังเกตได้ว่าการเพิ่มขั้นตอนใน for loop 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.