Perlにファイルハンドルがあり、ファイルFILE
のすべての行を繰り返し処理したいと思います。次の違いはありますか?
while (<FILE>) {
# do something
}
と
foreach (<FILE>) {
# do something
}
Perlにファイルハンドルがあり、ファイルFILE
のすべての行を繰り返し処理したいと思います。次の違いはありますか?
while (<FILE>) {
# do something
}
と
foreach (<FILE>) {
# do something
}
ほとんどの場合、違いに気付かないでしょう。ただし、foreach
各行を(配列ではなく)リストに読み込んでから、1行ずつ読み取りますが、一度に1行ずつ読み取ります。より多くのメモリを使用し、事前に処理時間を必要とするため、通常、ファイルの行を反復処理するために使用することをお勧めします。while
foreach
while
編集(Schwern経由):foreach
ループはこれと同等です:
my @lines = <$fh>;
for my $line (@lines) {
...
}
残念ながら、Perlは範囲演算子()の場合のようにこの特殊なケースを最適化しません1..10
。
たとえば、for
ループとループを使用して/ usr / share / dict / wordsを読み取り、それらが完了したらスリープ状態にすると、プロセスが消費しているメモリの量を確認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
このプログラムは、2.4メガの/ usr / share / dict / wordsの内容を格納するためにfor
、約32メガの実メモリ(列)を消費しています。RSS
ループはwhile
一度に1行しか格納せず、行のバッファリングに70kしか消費しません。
スカラーコンテキスト(つまりwhile
)では<FILE>
、各行を順番に返します。
リストコンテキスト(つまりforeach
)では<FILE>
、ファイルの各行で構成されるリストを返します。
while
コンストラクトを使用する必要があります。
詳細については、perlop-I/O演算子を参照してください。
編集: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";
}
}
これを扱った例を、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 random hacker はコメントで、ファイル ハンドルから読み取るときに、Perl が while ループで falseness テストを特殊なケースにしていることを指摘しています。偽の値を読み取ってもループが終了しないことを確認しました -- 少なくとも最近の perl では。間違った方向に進んで申し訳ありません。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 番目の例は、2 GB の RAM しかないマシンで大きな (たとえば 3 GB の) ファイルを解析する必要がある場合です。foreach
単にメモリ不足になり、クラッシュします。私はこれを、perl プログラミングの人生の非常に早い段階で難しい方法で学びました。
foreach ループは while (条件付き) よりも高速です。