Có vẻ như vòng lặp vô tận kết thúc, trừ khi System.out.println được sử dụng


91

Tôi đã có một đoạn mã đơn giản được cho là một vòng lặp vô tận vì xnó sẽ luôn phát triển và sẽ luôn lớn hơn j.

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

nhưng như vậy, nó in yvà không lặp lại liên tục. Tôi không thể tìm ra lý do tại sao. Tuy nhiên, khi tôi điều chỉnh mã theo cách sau:

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

Nó trở thành một vòng lặp vô tận và tôi không biết tại sao. Liệu java có nhận ra vòng lặp vô tận của nó và bỏ qua nó trong tình huống đầu tiên nhưng phải thực hiện lệnh gọi phương thức trong tình huống thứ hai để nó hoạt động như mong đợi không? Bối rối :)


4
Vòng lặp thứ hai là vô tận vì giới hạn trên xphát triển nhanh hơn biến vòng lặp j. Nói cách khác, jsẽ không bao giờ đạt đến giới hạn trên, do đó vòng lặp sẽ chạy "mãi mãi". Chà, không phải mãi mãi, rất có thể bạn sẽ bị tràn vào một lúc nào đó.
Tim Biegeleisen

75
Nó không phải là một vòng lặp vô tận, nó chỉ là phải mất 238609294 lần để lặp để ra khỏi vòng lặp for trong trường hợp đầu tiên và lần thứ hai nó in giá trị của y238609294 lần
n00b Pr0grammer

13
câu trả lời một từ: tràn
qwr

20
Amusingly, System.out.println(x)thay vì yở cuối sẽ thấy ngay lập tức những gì vấn đề là
JollyJoker

9
@TeroLahtinen không, nó sẽ không. Đọc thông số kỹ thuật của ngôn ngữ Java nếu bạn nghi ngờ kiểu int là gì. Nó độc lập với phần cứng.
9ilsdx 9rvj 0lo

Câu trả lời:


161

Cả hai ví dụ không phải là vô tận.

Vấn đề là giới hạn của intkiểu trong Java (hoặc khá nhiều ngôn ngữ phổ biến khác). Khi giá trị xđạt tới 0x7fffffff, việc thêm bất kỳ giá trị dương nào sẽ dẫn đến tràn và giá trị xtrở thành âm, do đó thấp hơn j.

Sự khác biệt giữa vòng lặp thứ nhất và thứ hai là mã bên trong mất nhiều thời gian hơn và có thể mất vài phút cho đến khi xtràn. Đối với ví dụ đầu tiên, có thể mất ít hơn thứ hai hoặc hầu hết có thể mã sẽ bị xóa bởi trình tối ưu hóa vì nó không có bất kỳ tác dụng nào.

Như đã đề cập trong phần thảo luận, thời gian sẽ phụ thuộc nhiều vào cách hệ điều hành đệm đầu ra, liệu nó có xuất ra trình giả lập đầu cuối hay không, v.v., vì vậy nó có thể cao hơn nhiều so với vài phút.


48
Tôi vừa thử một chương trình (trên máy tính xách tay của tôi) in một dòng trong một vòng lặp. Tôi đã hẹn giờ nó và nó có thể in khoảng 1000 dòng / giây. Dựa trên nhận xét của N00b rằng vòng lặp sẽ thực hiện 238609294 lần, sẽ mất khoảng 23861 giây để vòng lặp kết thúc - hơn 6,6 giờ. Hơn "vài phút".
ajb

11
@ajb: Tùy vào việc thực hiện. IIRC println()trên Windows là một hoạt động chặn, trong khi trên (một số?) Unix, nó được lưu vào bộ đệm để diễn ra nhanh hơn nhiều. Ngoài ra hãy thử sử dụng print(), mà bộ đệm cho đến khi nó chạm một \n(hoặc lấp đầy bộ đệm, hoặc flush()được gọi)
BlueRaja - Danny Pflughoeft

6
Ngoài ra nó phụ thuộc vào thiết bị đầu cuối hiển thị đầu ra. Xem stackoverflow.com/a/21947627/53897 để biết một ví dụ đặc biệt (trong đó tốc độ chậm là do gói từ)
Thorbjørn Ravn Andersen

1
Có, nó được lưu vào bộ đệm trên UNIX, nhưng nó vẫn đang chặn. Khi bộ đệm 8K hoặc lâu hơn đầy, nó sẽ chặn cho đến khi có chỗ. Tốc độ sẽ phụ thuộc nhiều vào tốc độ tiêu thụ của nó. Chuyển hướng đầu ra đến / dev / null sẽ là nhanh nhất, nhưng khi nó được gửi đến thiết bị đầu cuối, theo mặc định, sẽ yêu cầu cập nhật đồ họa cho màn hình và nhiều sức mạnh tính toán hơn vì nó làm cho các phông chữ làm chậm nó.
penguin359

2
@Zbynek ồ, có lẽ vậy, nhưng điều đó nhắc tôi nhớ rằng, I / O thiết bị đầu cuối thường sẽ được đệm dòng chứ không phải khối, vì vậy rất có thể mọi println sẽ dẫn đến lệnh gọi hệ thống làm chậm thêm trường hợp đầu cuối.
penguin359

33

Vì chúng được khai báo là int, một khi nó đạt đến giá trị lớn nhất, vòng lặp sẽ bị phá vỡ vì giá trị x sẽ trở thành số âm.

Nhưng khi System.out.println được thêm vào vòng lặp, tốc độ thực thi sẽ hiển thị (vì việc xuất ra bảng điều khiển sẽ làm chậm tốc độ thực thi). Tuy nhiên, nếu bạn để chương trình thứ 2 (chương trình có syso bên trong vòng lặp) chạy đủ lâu, nó sẽ có cùng hành vi với chương trình đầu tiên (chương trình không có syso bên trong vòng lặp).


21
Mọi người không nhận ra rằng việc gửi thư rác vào bảng điều khiển có thể làm chậm mã của họ đến mức nào.
user9993,

13

Có thể có hai lý do cho điều này:

  1. Java tối ưu hóa forvòng lặp và vì không sử dụng xsau vòng lặp nên chỉ cần loại bỏ vòng lặp. Bạn có thể kiểm tra điều này bằng cách đặt System.out.println(x);câu lệnh sau vòng lặp.

  2. Có thể Java không thực sự tối ưu hóa vòng lặp và nó đang thực thi chương trình một cách chính xác và cuối cùng xsẽ phát triển quá lớn intvà tràn. Số nguyên tràn phần lớn có thể sẽ làm cho số nguyên xlà âm sẽ nhỏ hơn j và vì vậy nó sẽ thoát ra khỏi vòng lặp và in ra giá trị của y. Điều này cũng có thể được kiểm tra bằng cách thêm vào System.out.println(x);sau vòng lặp.

Ngoài ra, ngay cả trong trường hợp đầu tiên cuối cùng tràn sẽ xảy ra do đó hiển thị nó trong trường hợp thứ hai, do đó nó sẽ không bao giờ là một vòng lặp vô tận thực sự.


14
Tôi chọn cửa số 2
Robby Cornelissen

Thật. Nó đi vào thang âm và thoát khỏi vòng lặp. Nhưng a sysoutquá chậm để thêm ảo tưởng về một vòng lặp vô hạn.
Pavan Kumar

4
1. Sẽ là một lỗi. Tối ưu hóa trình biên dịch không được phép thay đổi hoạt động của chương trình. Nếu đây là một vòng lặp vô hạn, thì trình biên dịch có thể tối ưu hóa tất cả những gì nó muốn, tuy nhiên, kết quả vẫn phải là một vòng lặp vô hạn. Giải pháp thực sự là OP đã nhầm lẫn: cả hai đều không phải là một vòng lặp vô hạn, một cái chỉ làm nhiều việc hơn cái kia, vì vậy nó mất nhiều thời gian hơn.
Jörg W Mittag

1
@ JörgWMittag Trong trường hợp này x là một biến cục bộ không có quan hệ với bất kỳ thứ gì khác. Vì vậy, có thể nó đã được tối ưu hóa. Nhưng người ta nên nhìn vào mã bytecode để xác định xem có đúng như vậy không, đừng bao giờ chỉ cho rằng trình biên dịch đã làm điều gì đó như vậy.
HopeHelpful

1

Cả hai đều không phải là vòng lặp vô tận, ban đầu là j = 0, miễn là j <x, j tăng (j ++) và j là một số nguyên nên vòng lặp sẽ chạy cho đến khi đạt giá trị lớn nhất sau đó tràn (Điều kiện tràn số nguyên là xảy ra khi kết quả của một phép toán số học, chẳng hạn như phép nhân hoặc phép cộng, vượt quá kích thước tối đa của kiểu số nguyên được sử dụng để lưu trữ nó.). đối với ví dụ thứ hai, hệ thống chỉ in giá trị của y cho đến khi vòng lặp bị ngắt.

nếu bạn đang tìm kiếm một ví dụ về một vòng lặp vô tận, nó sẽ giống như thế này

int x = 6;

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

bởi vì (x) sẽ không bao giờ đạt được giá trị 10;

bạn cũng có thể tạo một vòng lặp vô hạn với vòng lặp đôi for:

int i ;

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

vòng lặp này là vô hạn vì vòng lặp for đầu tiên cho biết i <10, điều này đúng nên nó chuyển sang vòng lặp for thứ hai và vòng lặp for thứ hai tăng giá trị của (i) cho đến khi nó là == 5. Sau đó, nó tiếp tục vào vòng lặp thứ nhất vòng lặp for lại vì i <10, quá trình tiếp tục lặp lại chính nó vì nó đặt lại sau vòng lặp for thứ hai


1

Đó là một vòng lặp hữu hạn vì một khi giá trị của x vượt quá 2,147,483,647(là giá trị lớn nhất của an int), xsẽ trở thành số âm và không lớn hơn jbất kỳ nào, cho dù bạn có in y hay không.

Bạn chỉ có thể thay đổi giá trị của ythành 100000và in ytrong vòng lặp và vòng lặp sẽ sớm bị phá vỡ.

Lý do tại sao bạn cảm thấy nó trở nên vô hạn là do System.out.println(y);mã được thực thi chậm hơn rất nhiều so với không có bất kỳ hành động nào.


0

Vấn đề thú vị Thực ra trong cả hai trường hợp, vòng lặp không phải là vô tận

Nhưng sự khác biệt chính giữa chúng là khi nào nó sẽ kết thúc và mất bao nhiêu thời gian xđể vượt quá intgiá trị tối đa là2,147,483,647 sau đó nó sẽ đạt đến trạng thái tràn và vòng lặp sẽ kết thúc.

Cách tốt nhất để hiểu vấn đề này là kiểm tra một ví dụ đơn giản và bảo toàn kết quả của nó.

Ví dụ :

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

Đầu ra:

finished!
BUILD SUCCESSFUL (total time: 0 seconds)

Sau khi thử nghiệm vòng lặp vô hạn này, sẽ mất chưa đầy 1 giây để kết thúc.

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

Đầu ra:

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

Trong trường hợp thử nghiệm này, bạn sẽ nhận thấy sự khác biệt rất lớn về thời gian thực hiện để kết thúc và chạy chương trình.

Nếu bạn không kiên nhẫn, bạn sẽ nghĩ rằng vòng lặp này là vô tận và sẽ không kết thúc nhưng thực tế sẽ mất hàng giờ để kết thúc và đạt đến trạng thái tràn tại igiá trị.

Cuối cùng, chúng tôi kết luận sau khi chúng tôi đặt câu lệnh print bên trong vòng lặp for rằng nó sẽ mất nhiều thời gian hơn vòng lặp trong trường hợp đầu tiên không có câu lệnh print.

Thời gian cần thiết để chạy chương trình phụ thuộc vào thông số kỹ thuật máy tính của bạn, cụ thể là sức mạnh xử lý (dung lượng bộ xử lý), hệ điều hành và IDE của bạn đang biên dịch chương trình.

Tôi kiểm tra trường hợp này trên:

Lenovo Intel Core i5 2,7 GHz

Hệ điều hành: Windows 8.1 64x

IDE: NetBeans 8.2

Mất khoảng 8 giờ (486 phút) để kết thúc chương trình.

Ngoài ra, bạn có thể nhận thấy rằng bước tăng trong vòng lặp for i = i + 1 là yếu tố rất chậm để đạt đến giá trị int tối đa.

Chúng tôi có thể thay đổi hệ số này và tăng số bước nhanh hơn để kiểm tra vòng lặp trong thời gian ngắn hơn.

nếu chúng tôi đặt i = i * 10và kiểm tra nó:

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

Đầu ra:

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

Như bạn thấy, nó rất nhanh so với vòng lặp trước đó

chỉ mất chưa đầy 1 giây để kết thúc và chạy chương trình.

Sau ví dụ thử nghiệm này, tôi nghĩ rằng nó sẽ làm rõ vấn đề và chứng minh tính hợp lệ của câu trả lời của Zbynek Vyskovsky - kvr000 , nó cũng sẽ là câu trả lời cho câu hỏi này .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.