34

我在 Perl 中有一个文件句柄FILE,我想遍历文件中的所有行。以下有区别吗?

while (<FILE>) {
    # do something
}

foreach (<FILE>) {
    # do something
}
4

8 回答 8

40

对于大多数用途,您可能不会注意到差异。但是,在逐行遍历之前将foreach每一行读入列表而不是数组while),而一次读取一行。由于foreach会使用更多内存并需要预先处理时间,因此通常建议使用while遍历文件的行。

编辑(通过 Schwern):foreach循环等效于:

my @lines = <$fh>;
for my $line (@lines) {
    ...
}

不幸的是,Perl 没有像使用范围运算符 ( 1..10) 那样优化这种特殊情况。

例如,如果我使用for循环和while循环读取 /usr/share/dict/words 并让它们在完成后休眠,我可以使用它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循环一次只存储一条线,仅消耗 70k 用于线缓冲。

于 2009-02-25T09:50:32.013 回答
19

在标量上下文中 (ie while)<FILE>依次返回每一行。

在列表上下文中(即foreach<FILE>返回一个由文件中的每一行组成的列表。

您应该使用该while构造。

有关更多信息,请参阅perlop - I/O 运算符

编辑:j_random_hacker 正确地说

while (<FILE>) { … }

践踏$_而 foreach 没有(foreach$_首先本地化)。当然,这是重要的行为差异!

于 2009-02-25T09:46:38.077 回答
11

除了前面的回复,使用的另一个好处while是可以使用$.变量。这是最后访问的文件句柄的当前行号(请参阅 参考资料perldoc perlvar)。

while ( my $line = <FILE> ) {
    if ( $line =~ /some_target/ ) {
        print "Found some_target at line $.\n";
    }
}
于 2009-02-25T15:43:37.033 回答
4

我在下一版Effective Perl Programming中添加了一个处理这个问题的示例。

使用 a while,您可以停止处理FILE并仍然获得未处理的行:

 while( <FILE> ) {  # scalar context
      last if ...;
      }
 my $line = <FILE>; # still lines left

如果你使用 a ,即使你停止处理它们,你也会foreach消耗掉所有的行:foreach

 foreach( <FILE> ) { # list context
      last if ...;
      }
 my $line = <FILE>; # no lines left!
于 2010-02-04T02:38:20.057 回答
3

更新:j random hacker 在评论中指出 Perl 在读取文件句柄时会在 while 循环中进行错误测试。我刚刚验证了读取错误值不会终止循环——至少在现代 perls 上。对不起,把你们都搞错了。在编写 Perl 15 年后,我仍然是个菜鸟。;)

上面的每个人都是对的:使用while循环,因为它会更节省内存并为您提供更多控制权。

不过,关于该循环的一个有趣while之处在于,当读取为假时它会退出。通常这将是文件结尾,但如果它返回一个空字符串或一个 0 怎么办?哎呀!您的程序退出得太早了。如果文件中的最后一行没有换行符,这可能发生在任何文件句柄上。它也可能发生在具有 read 方法的自定义文件对象上,该方法不像常规 Perl 文件对象那样处理换行符。

这是修复它的方法。检查表示文件结束的未定义值读取:

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

顺便说一句,foreach循环没有这个问题,即使效率低下也是正确的。

于 2009-02-26T01:06:08.803 回答
2

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.

于 2009-08-29T11:30:55.793 回答
1

这是一个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 中也会有解决这个问题的方法(我想到了读入数组),但肯定提供了一个非常直接的解决方案。

第二个示例是当您必须在只有 2GB RAM 的机器上解析一个大(比如 3GB)文件时。foreach只会耗尽内存并崩溃。我在 perl 编程生涯的早期就很难学会这一点。

于 2010-03-11T14:08:28.507 回答
0

foreach 循环比 while (基于条件的)更快。

于 2010-08-20T01:52:33.963 回答