В чем разница между итерацией по файлу с foreach или в Perl? - PullRequest
32 голосов
/ 25 февраля 2009

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

while (<FILE>) {
    # do something
}

и

foreach (<FILE>) {
    # do something
}

Ответы [ 8 ]

37 голосов
/ 25 февраля 2009

В большинстве случаев вы, вероятно, не заметите разницу. Однако 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 / share / dict / words. Цикл while хранит только одну строку за раз, затрачивая всего 70 Кб для буферизации строки.

19 голосов
/ 25 февраля 2009

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

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

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

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

Редактировать: j_random_hacker справедливо говорит, что

while (<FILE>) { … }

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

10 голосов
/ 25 февраля 2009

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

while ( my $line = <FILE> ) {
    if ( $line =~ /some_target/ ) {
        print "Found some_target at line $.\n";
    }
}
3 голосов
/ 04 февраля 2010

Я добавил пример, связанный с этим, в следующую редакцию Эффективное программирование на 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!
2 голосов
/ 26 февраля 2009

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

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

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

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

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

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

1 голос
/ 11 марта 2010

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

1 голос
/ 29 августа 2009

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.

0 голосов
/ 20 августа 2010

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

...