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

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

while (<FILE>) {
    # do something
}

а также

foreach (<FILE>) {
    # do something
}

Ответов (8)

Решение

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

РЕДАКТИРОВАТЬ (через Шверна): 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 / доли / Dict / слов. while Петля хранит только одну строки в то время , потребляющей только 70k для линии буферизации.

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 .

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

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

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

См. Perlop - Операторы ввода-вывода для получения дополнительной информации.

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

while (<FILE>) { … }

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

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

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

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

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

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

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

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

foreach Цикл не имеет этой проблемы, кстати , и является правильным , даже если неэффективно.

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

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

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

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

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

Вот пример, где 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.

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