Подтвердить что ты не робот

Какая разница между итерацией по файлу с помощью foreach или while в Perl?

У меня есть дескриптор файла FILE в Perl, и я хочу перебирать все строки в файле. Есть ли разница между следующими?

while (<FILE>) {
    # do something
}

и

foreach (<FILE>) {
    # do something
}
4b9b3361

Ответ 1

Для большинства целей вы, вероятно, не заметите разницы. Тем не менее, foreach читает каждую строку в список (не массив), прежде чем проходить через нее по строкам, тогда как while читает по одной строке за раз. Поскольку foreach будет использовать больше памяти и требует предварительной обработки, обычно рекомендуется использовать while для итерации по строкам файла.

EDIT (через Schwern): цикл foreach эквивалентен этому:

my @lines = <$fh>;
for my $line (@lines) {
    ...
}

Несчастливо, что Perl не оптимизирует этот частный случай, как это делает оператор диапазона (1..10).

Например, если я читаю /usr/share/dict/words с циклом for и циклом while и сплю, когда они будут выполнены, я могу использовать ps, чтобы узнать, сколько памяти процесс потребляет. В качестве элемента управления я включил программу, которая открывает файл, но ничего не делает с ним.

USER       PID %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
schwern  73019   0.0  1.6   625552  33688 s000  S     2:47PM   0:00.24 perl -wle open my $fh, shift; for(<$fh>) { 1 } print "Done";  sleep 999 /usr/share/dict/words
schwern  73018   0.0  0.1   601096   1236 s000  S     2:46PM   0:00.09 perl -wle open my $fh, shift; while(<$fh>) { 1 } print "Done";  sleep 999 /usr/share/dict/words
schwern  73081   0.0  0.1   601096   1168 s000  S     2:55PM   0:00.00 perl -wle open my $fh, shift; print "Done";  sleep 999 /usr/share/dict/words

Программа for потребляет почти 32 мегабайта реальной памяти (столбец RSS) для хранения содержимого моих 2,4 мега /usr/share/dict/words. Цикл while хранит только одну строку за время, затрачивающую всего 70 тыс. Для буферизации строк.

Ответ 2

В скалярном контексте (т.е. while) <FILE> возвращает каждую строку по очереди.

В контексте списка (т.е. foreach) <FILE> возвращает список, состоящий из каждой строки из файла.

Вы должны использовать конструкцию while.

Подробнее см. perlop - Операторы ввода/вывода.

Изменить: j_random_hacker справедливо говорит, что

while (<FILE>) { … }

растаптывается на $_, а foreach не делает (сначала локализует $_). Несомненно, это самая важная поведенческая разница!

Ответ 3

В дополнение к предыдущим ответам, еще одно преимущество использования while заключается в том, что вы можете использовать переменную $.. Это текущий номер строки последней доступной рукописи файла (см. perldoc perlvar).

while ( my $line = <FILE> ) {
    if ( $line =~ /some_target/ ) {
        print "Found some_target at line $.\n";
    }
}

Ответ 4

Я добавил пример, относящийся к следующему выпуску Эффективное программирование на Perl.

С помощью while вы можете прекратить обработку FILE и по-прежнему получать необработанные строки:

 while( <FILE> ) {  # scalar context
      last if ...;
      }
 my $line = <FILE>; # still lines left

Если вы используете foreach, вы потребляете все строки в foreach, даже если вы прекратите их обрабатывать:

 foreach( <FILE> ) { # list context
      last if ...;
      }
 my $line = <FILE>; # no lines left!

Ответ 5

Обновление: j случайный хакер указывает в комментарии, что специальные случаи Perl проверяют ложность в цикле while при чтении из дескриптора файла. Я только что проверил, что чтение ложного значения не приведет к завершению цикла - по крайней мере, на современных perls. Извините за то, что вы все ошибаетесь. После 15 лет написания Perl я все еще ноб.;)

Все вышеописанное правильно: используйте цикл while, потому что он будет более эффективным с точки зрения памяти и даст вам больше контроля.

Забавно, что цикл while заключается в том, что он выходит, когда чтение является ложным. Обычно это будет конец файла, но что, если он возвращает пустую строку или 0? К сожалению! Ваша программа просто вышла слишком рано. Это может произойти в любом дескрипторе файла, если последняя строка в файле не имеет новой строки. Это также может происходить с пользовательскими объектами файлов, которые имеют метод чтения, который не обрабатывает новые строки так же, как обычные объекты файлов Perl.

Вот как это исправить. Проверьте наличие значения undefined, которое указывает на конец файла:

while (defined(my $line = <FILE>)) {
    print $line;
}

В цикле foreach нет этой проблемы, и она правильная, хотя и неэффективная.

Ответ 6

j_random_hacker упомянул об этом в комментариях к этому ответу, но не сделал на самом деле положил его в свой ответ, хотя это еще одно различие стоит упомянуть.

Отличие состоит в том, что while (<FILE>) {} перезаписывает $_, а foreach(<FILE>) {} локализует его. То есть:

$_ = 100;
while (<FILE>) {
    # $_ gets each line in turn
    # do something with the file
}
print $_; # yes I know that $_ is unneeded here, but 
          # I'm trying to write clear code for the example

Распечатает последнюю строку <FILE>.

Однако

$_ = 100;
foreach(<FILE>) {
    # $_ gets each line in turn
    # do something with the file
}
print $_;

Выведет 100. Чтобы получить то же самое с конструкцией while(<FILE>) {}, вам нужно будет сделать:

$_ = 100;
{
    local $_;
    while (<FILE>) {
        # $_ gets each line in turn
        # do something with the file
    }
}
print $_; # yes I know that $_ is unneeded here, but 
          # I'm trying to write clear code for the example

Теперь это напечатает 100.

Ответ 7

Вот пример, где foreach не будет работать, но while выполнит задание

while (<FILE>) {
   $line1 = $_;
   if ($line1 =~ /SOMETHING/) {
      $line2 = <FILE>;
      if (line2 =~ /SOMETHING ELSE/) {
         print "I found SOMETHING and SOMETHING ELSE in consecutive lines\n";
         exit();
      }
   }
}

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

Второй пример - когда вам приходится разбирать большой (скажем, 3 ГБ) файл на вашем компьютере с 2 ГБ оперативной памяти. foreach будет просто закончиться память и сбой. Я изучил этот трудный путь очень рано в моей жизни программирования perl.

Ответ 8

цикл foreach быстрее, чем while (который является условным).