PerlにファイルハンドルFILE
があり、ファイル内のすべての行を反復処理したいと思います。以下に違いはありますか?
while (<FILE>) {
# do something
}
そして
foreach (<FILE>) {
# do something
}
ほとんどの場合、違いに気付かないでしょう。ただし、foreach
は、各行をlist( 配列ではない )に読み込んでから、一方、while
は一度に1行を読み取ります。 foreach
はより多くのメモリを使用し、事前に処理時間を必要とするため、通常はwhile
を使用してファイルの行を反復処理することをお勧めします。
編集(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
プログラムは、2.4メガの/ usr/share/dict/wordsの内容を格納するために、ほぼ32メガの実メモリー(RSS
列)を消費しています。 while
ループは、一度に1行しか格納せず、行のバッファリングに70kしか消費しません。
スカラーコンテキスト(つまり、while
)では、<FILE>
は各行を順番に返します。
リストコンテキスト(つまり、foreach
)では、<FILE>
はファイルの各行で構成されるリストを返します。
while
構文を使用する必要があります。
詳細については、 perlop --I/O演算子 を参照してください。
編集:j_random_hackerは正しくそれを言います
while (<FILE>) { … }
$_
を踏みつけますが、foreachは踏みつけません(foreachは$_
を最初にローカライズします)。確かにこれは最も重要な行動の違いです!
前の応答に加えて、while
を使用するもう1つの利点は、 $.
変数。これは、最後にアクセスされたファイルハンドルの現在の行番号です( perldoc perlvar
)。
while ( my $line = <FILE> ) {
if ( $line =~ /some_target/ ) {
print "Found some_target at line $.\n";
}
}
これを扱う例を次の版に追加しました Effective Perl Programming 。
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ループでfalseテストを特殊に処理することを指摘しています。false値を読み取ってもループが終了しないことを確認しました--at少なくとも現代のperlsでは。すべて間違った方向に進んで申し訳ありません。Perlを15年間書いた後も、私はまだ初心者です。;)
上記のすべての人が正しいです。while
ループを使用すると、メモリ効率が向上し、より詳細に制御できるようになります。
ただし、そのwhile
ループの面白い点は、読み取りがfalseのときに終了することです。通常、これはファイルの終わりになりますが、空の文字列または0を返す場合はどうなりますか?おっと!プログラムがすぐに終了しました。これは、ファイルの最後の行に改行がない場合、どのファイルハンドルでも発生する可能性があります。また、通常のPerlファイルオブジェクトと同じように改行を処理しないreadメソッドを持つカスタムファイルオブジェクトでも発生する可能性があります。
修正方法は次のとおりです。ファイルの終わりを示す未定義の値の読み取りを確認します。
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(配列への読み取りが頭に浮かぶ)でもこの問題の回避策があると確信していますが、確かに非常に簡単な解決策を提供します。
2番目の例は、RAMが2GBしかないマシンで大きな(たとえば3GB)ファイルを解析する必要がある場合です。 foreach
は単にメモリを使い果たし、クラッシュします。私はPerlプログラミングの人生の非常に早い段階でこれを難しい方法で学びました。
foreachループはwhileよりも高速です(条件ベース)。