我在 Perl 中有一个文件句柄FILE
,我想遍历文件中的所有行。以下有区别吗?
while (<FILE>) {
# do something
}
和
foreach (<FILE>) {
# do something
}
我在 Perl 中有一个文件句柄FILE
,我想遍历文件中的所有行。以下有区别吗?
while (<FILE>) {
# do something
}
和
foreach (<FILE>) {
# do something
}
对于大多数用途,您可能不会注意到差异。但是,在逐行遍历之前将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 用于线缓冲。
在标量上下文中 (ie 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中添加了一个处理这个问题的示例。
使用 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!
更新:j random hacker 在评论中指出 Perl 在读取文件句柄时会在 while 循环中进行错误测试。我刚刚验证了读取错误值不会终止循环——至少在现代 perls 上。对不起,把你们都搞错了。在编写 Perl 15 年后,我仍然是个菜鸟。;)
上面的每个人都是对的:使用while
循环,因为它会更节省内存并为您提供更多控制权。
不过,关于该循环的一个有趣while
之处在于,当读取为假时它会退出。通常这将是文件结尾,但如果它返回一个空字符串或一个 0 怎么办?哎呀!您的程序退出得太早了。如果文件中的最后一行没有换行符,这可能发生在任何文件句柄上。它也可能发生在具有 read 方法的自定义文件对象上,该方法不像常规 Perl 文件对象那样处理换行符。
这是修复它的方法。检查表示文件结束的未定义值读取:
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 中也会有解决这个问题的方法(我想到了读入数组),但肯定提供了一个非常直接的解决方案。
第二个示例是当您必须在只有 2GB RAM 的机器上解析一个大(比如 3GB)文件时。foreach
只会耗尽内存并崩溃。我在 perl 编程生涯的早期就很难学会这一点。
foreach 循环比 while (基于条件的)更快。