Здається, нескінченний цикл припиняється, якщо не використовується 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. Іншими словами, jніколи не потрапить на верхню межу, отже, цикл працюватиме "назавжди". Ну, не назавжди, швидше за все ви отримаєте перелив.
Тім Біглейзен

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, додавання будь-якого позитивного значення призведе до переповнення, а значення xстане негативним, отже, нижче ніж j.

Різниця між першим і другим циклом полягає в тому, що внутрішній код займає набагато більше часу, і, можливо, знадобиться кілька хвилин, поки xпереповнення. Для першого прикладу це може зайняти менше секунди або, швидше за все, код буде видалений оптимізатором, оскільки це не має жодного ефекту.

Як згадувалося в обговоренні, час буде сильно залежати від того, як ОС буферизує вихід, чи виводиться він на емулятор терміналу тощо, тому може бути набагато більшим, ніж кілька хвилин.


48
Я щойно спробував програму (на своєму ноутбуці), яка друкує рядок у циклі. Я визначив час, і він зміг надрукувати приблизно 1000 рядків / секунду. На основі коментаря 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 буде найшвидшим, але якщо його відправити на термінал, за замовчуванням, потрібні графічні оновлення на екрані та набагато більше обчислювальної потужності, оскільки шрифти сповільнюють його.
пінгвін359

2
@Zbynek, мабуть, так, але це мені нагадує, термінали вводу-виводу зазвичай буферизуються, а не блокуються, тому, швидше за все, кожен println призведе до системного дзвінка, що ще більше уповільнить корпус терміналу.
пінгвін359 02

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від’ємним, яке буде менше j, і тому воно вийде з циклу і виведе значення y. Це також можна перевірити, додавши System.out.println(x);після циклу.

Крім того, навіть у першому випадку врешті-решт відбудеться переповнення, що призведе до другого випадку, так що це ніколи не буде справжнім нескінченним циклом.


14
Я вибираю двері № 2.
Роббі Корнеліссен,

Правда. Він пішов за негативною шкалою і вийшов із циклу. Але a sysoutтак повільно додає ілюзію нескінченної петлі.
Паван Кумар,

4
1. Була б помилка. Оптимізація компілятора не має права змінювати поведінку програми. Якщо це був нескінченний цикл, то компілятор може оптимізувати все, що хоче, однак результат все одно повинен бути нескінченним циклом. Реальне рішення полягає в тому, що ОП помиляється: жоден з двох не є нескінченним циклом, один просто робить більше роботи, ніж інший, тому це займає більше часу.
Jörg W Mittag

1
@ JörgWMittag У цьому випадку x є локальною змінною, не пов'язаною ні з чим іншим. Як такий, можливо, його оптимізують. Але слід поглянути на байт-код, щоб визначити, чи це так, ніколи не просто припускати, що компілятор зробив щось подібне.
Сподіваємось,

1

Вони обидва не є нескінченними циклами, спочатку j = 0, доки j <x, j збільшується (j ++), а j є цілим числом, тому цикл буде працювати, поки не досягне максимального значення, а потім переповнюється (умовою переповнення цілого числа є умова це відбувається, коли результат арифметичної операції, наприклад множення або додавання, перевищує максимальний розмір цілочисельного типу, що використовується для його збереження.). для другого прикладу система просто друкує значення y, поки цикл не розірветься.

якщо ви шукаєте приклад нескінченного циклу, він повинен виглядати так

int x = 6;

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

тому що (x) ніколи не досягне значення 10;

Ви також можете створити нескінченний цикл з подвійним циклом for:

int i ;

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

цей цикл нескінченний, тому що перший цикл for говорить i <10, що відповідає дійсності, тому він переходить у другий цикл for, а другий цикл for збільшує значення (i), доки не буде == 5. Потім він переходить у перший для циклу знову, тому що i <10, процес продовжує повторюватися, оскільки скидається після другого циклу for


1

Це скінченний цикл, тому що коли значення xперевищує 2,147,483,647(що є максимальним значенням an int), воно xстане негативним і не буде більшим j, незалежно від того, друкуєте ви y чи ні.

Ви можете просто змінити значення yto 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значенням.

Нарешті, після того, як ми помістили оператор print усередину for циклу, ми дійшли висновку, що це займе набагато більше часу, ніж цикл у першому випадку без оператора print.

Час, необхідний для запуску програми, залежить від технічних характеристик вашого комп’ютера, зокрема обчислювальної потужності (потужності процесора), операційної системи та вашої IDE, яка складає програму.

Я перевіряю цей випадок на:

Lenovo 2,7 ГГц Intel Core i5

ОС: 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 секунди.

Після цього тестового прикладу, я думаю, він повинен прояснити проблему та довести обгрунтованість відповіді Збинека Висковського - kvr000 , також він буде відповіддю на це питання .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.