web-dev-qa-db-ja.com

foreachを使用してファイルを反復処理する場合とPerlを使用している場合の違いは何ですか?

PerlにファイルハンドルFILEがあり、ファイル内のすべての行を反復処理したいと思います。以下に違いはありますか?

while (<FILE>) {
    # do something
}

そして

foreach (<FILE>) {
    # do something
}
32
Nathan Fellman

ほとんどの場合、違いに気付かないでしょう。ただし、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しか消費しません。

37
Alex Reynolds

スカラーコンテキスト(つまり、while)では、<FILE>は各行を順番に返します。

リストコンテキスト(つまり、foreach)では、<FILE>はファイルの各行で構成されるリストを返します。

while構文を使用する必要があります。

詳細については、 perlop --I/O演算子 を参照してください。

編集:j_random_hackerは正しくそれを言います

while (<FILE>) { … }

$_を踏みつけますが、foreachは踏みつけません(foreachは$_を最初にローカライズします)。確かにこれは最も重要な行動の違いです!

19
kmkaplan

前の応答に加えて、whileを使用するもう1つの利点は、 $. 変数。これは、最後にアクセスされたファイルハンドルの現在の行番号です( perldoc perlvar )。

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

これを扱う例を次の版に追加しました 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!
3
brian d foy

更新:jランダムハッカーはコメントで、Perlがファイルハンドルから読み取るときにwhileループでfalseテストを特殊に処理することを指摘しています。false値を読み取ってもループが終了しないことを確認しました--at少なくとも現代のperlsでは。すべて間違った方向に進んで申し訳ありません。Perlを15年間書いた後も、私はまだ初心者です。;)

上記のすべての人が正しいです。whileループを使用すると、メモリ効率が向上し、より詳細に制御できるようになります。

ただし、そのwhileループの面白い点は、読み取りがfalseのときに終了することです。通常、これはファイルの終わりになりますが、空の文字列または0を返す場合はどうなりますか?おっと!プログラムがすぐに終了しました。これは、ファイルの最後の行に改行がない場合、どのファイルハンドルでも発生する可能性があります。また、通常のPerlファイルオブジェクトと同じように改行を処理しないreadメソッドを持つカスタムファイルオブジェクトでも発生する可能性があります。

修正方法は次のとおりです。ファイルの終わりを示す未定義の値の読み取りを確認します。

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

ちなみに、foreachループにはこの問題はなく、非効率的ですが正しいです。

2
Ken Fox

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_が出力されます。

1
Nathan Fellman

これは、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プログラミングの人生の非常に早い段階でこれを難しい方法で学びました。

1
user291535

foreachループはwhileよりも高速です(条件ベース)。

0
Von Tech