Казалось бы, бесконечный цикл завершается, если не используется 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никогда не достигнет верхней границы, следовательно, цикл будет работать «вечно». Ну, не навсегда, скорее всего, в какой-то момент у вас будет переполнение.
Тим Бигелейзен 01
75
Это не бесконечный цикл, просто для выхода из цикла for в первом случае требуется 238609294 раза, а во втором случае он печатает значение y238609294 раза
N00b Pr0grammer 01
13
ответ одним словом: переполнение
qwr 01
20
Забавно, но System.out.println(x)вместо того , чтобы yв конце сразу показать, в чем проблема
JollyJoker 01
9
@TeroLahtinen нет, это не так. Прочтите спецификацию языка Java, если сомневаетесь, что такое тип int. Он не зависит от оборудования.
9ilsdx 9rvj 0lo 01

Ответы:

161

Оба примера не бесконечны.

Проблема заключается в ограничении intтипа в Java (или почти любом другом распространенном языке). Когда значение xдостигает 0x7fffffff, добавление любого положительного значения приведет к переполнению, а значение xстанет отрицательным, поэтому будет меньше j.

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

Как упоминалось в обсуждении, время будет сильно зависеть от того, как ОС буферизует вывод, выводит ли он на эмулятор терминала и т. Д., Поэтому оно может быть намного больше нескольких минут.

Збынек Высковский - kvr000
источник
48
Я просто попробовал программу (на своем ноутбуке), которая печатает строку в цикле. Я рассчитал время, и он смог напечатать примерно 1000 строк в секунду. На основе комментария N00b о том, что цикл будет выполняться 238609294 раза, для завершения цикла потребуется около 23861 секунды - более 6,6 часов. Чуть больше "нескольких минут".
ajb 01
11
@ajb: зависит от реализации. IIRC println()в Windows - это операция блокировки, тогда как в (некоторых?) Unix она буферизуется, поэтому выполняется намного быстрее. Также попробуйте использовать print(), which buffers, пока он не достигнет \n(или буфер заполнится, или не flush()будет вызван)
BlueRaja - Дэнни Пфлугхефт 01
6
Также это зависит от терминала, отображающего вывод. См. Stackoverflow.com/a/21947627/53897 для крайнего примера (где замедление происходило из-за переноса слов)
Thorbjørn Ravn Andersen
1
Да, это буферизуется в UNIX, но все еще блокирует. Как только буфер 8K или около того заполнится, он будет заблокирован, пока не останется места. Скорость будет сильно зависеть от того, как быстро он потребляется. Перенаправление вывода в / dev / null будет самым быстрым, но для его отправки на терминал, по умолчанию, потребуются обновления графики на экране и гораздо больше вычислительной мощности, поскольку он отображает шрифты, замедляющие его.
penguin359
2
@Zbynek ой, наверное, да, но это напоминает мне, что ввод-вывод терминалов обычно будет буферизироваться по строке, а не блокироваться, поэтому, скорее всего, каждый println приведет к тому, что системный вызов еще больше замедлит работу терминала.
penguin359 02
33

Поскольку они объявлены как int, при достижении максимального значения цикл прервется, поскольку значение x станет отрицательным.

Но когда в цикл добавляется System.out.println, скорость выполнения становится видимой (поскольку вывод на консоль замедляет скорость выполнения). Однако, если вы позволите второй программе (с syso внутри цикла) работать достаточно долго, она должна иметь то же поведение, что и первая (та, которая без syso внутри цикла).

Эйс Захари
источник
21
Люди не понимают, сколько спама на консоли может замедлить их код.
user9993 01
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. Был бы баг. Оптимизациям компилятора не разрешается изменять поведение программы. Если это был бесконечный цикл, то компилятор может оптимизировать все, что захочет, однако результатом все равно должен быть бесконечный цикл. Реальное решение заключается в том, что OP ошибается: ни один из двух не является бесконечным циклом, просто один выполняет больше работы, чем другой, поэтому он занимает больше времени.
Jörg W Mittag
1
@ JörgWMittag В этом случае x - это локальная переменная, не имеющая отношения ни к чему другому. Таким образом, возможно, что он оптимизирован. Но следует посмотреть на байт-код, чтобы определить, так ли это, никогда не следует просто предполагать, что компилятор сделал что-то подобное.
HopefullyHelpful
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. Затем он перейдет в первый for цикл снова, потому что i <10, процесс продолжает повторяться, потому что он сбрасывается после второго цикла for

Кеннеди
источник
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значению потребуется несколько часов .

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

Время, необходимое для запуска программы, зависит от технических характеристик вашего компьютера, в частности от вычислительной мощности (мощности процессора), операционной системы и вашей среды разработки, которая компилирует программу.

Я тестирую этот случай на:

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 , также он будет ответом на этот вопрос .

Оглы
источник