У меня есть дескриптор файла FILE
в Perl, и я хочу перебирать все строки в файле. Есть ли разница между следующими?
while (<FILE>) {
# do something
}
и
foreach (<FILE>) {
# do something
}
У меня есть дескриптор файла FILE
в Perl, и я хочу перебирать все строки в файле. Есть ли разница между следующими?
while (<FILE>) {
# do something
}
и
foreach (<FILE>) {
# do something
}
Для большинства целей вы, вероятно, не заметите разницы. Тем не менее, 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 тыс. Для буферизации строк.
В скалярном контексте (т.е. while
) <FILE>
возвращает каждую строку по очереди.
В контексте списка (т.е. foreach
) <FILE>
возвращает список, состоящий из каждой строки из файла.
Вы должны использовать конструкцию while
.
Подробнее см. perlop - Операторы ввода/вывода.
Изменить: j_random_hacker справедливо говорит, что
while (<FILE>) { … }
растаптывается на
$_
, а foreach не делает (сначала локализует$_
). Несомненно, это самая важная поведенческая разница!
В дополнение к предыдущим ответам, еще одно преимущество использования while
заключается в том, что вы можете использовать переменную $.
. Это текущий номер строки последней доступной рукописи файла (см. perldoc perlvar
).
while ( my $line = <FILE> ) {
if ( $line =~ /some_target/ ) {
print "Found some_target at line $.\n";
}
}
Я добавил пример, относящийся к следующему выпуску Эффективное программирование на 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!
Обновление: j случайный хакер указывает в комментарии, что специальные случаи Perl проверяют ложность в цикле while при чтении из дескриптора файла. Я только что проверил, что чтение ложного значения не приведет к завершению цикла - по крайней мере, на современных perls. Извините за то, что вы все ошибаетесь. После 15 лет написания Perl я все еще ноб.;)
Все вышеописанное правильно: используйте цикл while
, потому что он будет более эффективным с точки зрения памяти и даст вам больше контроля.
Забавно, что цикл while
заключается в том, что он выходит, когда чтение является ложным. Обычно это будет конец файла, но что, если он возвращает пустую строку или 0? К сожалению! Ваша программа просто вышла слишком рано. Это может произойти в любом дескрипторе файла, если последняя строка в файле не имеет новой строки. Это также может происходить с пользовательскими объектами файлов, которые имеют метод чтения, который не обрабатывает новые строки так же, как обычные объекты файлов Perl.
Вот как это исправить. Проверьте наличие значения undefined, которое указывает на конец файла:
while (defined(my $line = <FILE>)) {
print $line;
}
В цикле foreach
нет этой проблемы, и она правильная, хотя и неэффективная.
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
.
Вот пример, где 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 (который является условным).