使用 foreach 或在 Perl 中迭代文件有啥区别?
Posted
技术标签:
【中文标题】使用 foreach 或在 Perl 中迭代文件有啥区别?【英文标题】:What's the difference between iterating over a file with foreach or while in Perl?使用 foreach 或在 Perl 中迭代文件有什么区别? 【发布时间】:2010-10-09 18:40:47 【问题描述】:我在 Perl 中有一个文件句柄 FILE
,我想遍历文件中的所有行。以下有区别吗?
while (<FILE>)
# do something
和
foreach (<FILE>)
# do something
【问题讨论】:
【参考方案1】:在大多数情况下,您可能不会注意到差异。然而,foreach
将每一行读入 list (not an array),然后逐行读取,而 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 用于行缓冲。
【讨论】:
该列表没有任何“假”。列表是正确的,数组是错误的。那里没有数组。 数组和列表的区别很重要。将它们混为一谈会导致您的理解错误,最终导致您的代码错误。 -1 直到您提到 while (foreach
使用列表还有另一个含义。如果文件句柄是一个保存另一个程序输出的管道,foreach
将等到管道关闭,因为只有这样它才能确定它将所有行读入数组,而while
只会阻塞直到\n
(或更准确地说,输入记录分隔符)通过管道发送。使用while
允许您在一个程序完成运行之前处理它的输出。【参考方案2】:
在标量上下文中(即while
)<FILE>
依次返回每一行。
在列表上下文中(即foreach
)<FILE>
返回一个由文件中的每一行组成的列表。
您应该使用while
构造。
请参阅perlop - I/O Operators 了解更多信息。
编辑:j_random_hacker 说得对
while (<FILE>) …
践踏
$_
而foreach 没有(foreach 首先本地化$_
)。这肯定是最重要的行为差异!
【讨论】:
-1 直到您提到 while ($_ = 42; foreach (@some_list) ...
之后,$_
是 42,因为在这种情况下 Perl 会自动本地化 $_
。但是在$_ = 42; while (<FILE>) ...
之后,$_
是从FILE
读取的最后一行(在读取整个文件的通常情况下是undef
)。这很烦人,因为foreach
行为更安全/更便于维护,但是使用foreach
读取文件意味着首先将整个文件读入内存,如果您有一个大文件并且逐行读取,这将非常浪费内存-line 处理就足够了!
非常感谢。这完全有道理。如果有一个循环机制来逐行读取文件处理程序并且仍然本地化 $_,那就太好了。【参考方案3】:
除了前面的回复,使用while
的另一个好处是您可以使用$.
变量。这是最后访问的文件句柄的当前行号(请参阅perldoc perlvar
)。
while ( my $line = <FILE> )
if ( $line =~ /some_target/ )
print "Found some_target at line $.\n";
【讨论】:
重新“访问”,具体而言,通过:readline/glob(又名 )、eof、tell、sysseek。 严格来说也可以访问$.使用 for 循环的变量;但由于它首先完全扩展列表,因此您总是会得到最后一个行号。【参考方案4】:我在下一版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!
【讨论】:
【参考方案5】:更新:j 随机黑客在评论中指出,当从文件句柄读取时,Perl 会在 while 循环中对错误测试进行特殊处理。我刚刚验证了读取错误值不会终止循环——至少在现代 perls 上。对不起,把你们都搞错了。在编写 Perl 15 年后,我仍然是个菜鸟。 ;)
上面的每个人都是对的:使用while
循环,因为它会更节省内存并为您提供更多控制权。
while
循环的一个有趣之处在于它在读取为假时退出。通常这将是文件结尾,但如果它返回一个空字符串或 0 怎么办?哎呀!您的程序退出得太早了。如果文件的最后一行没有换行符,这可能发生在任何文件句柄上。它也可能发生在具有读取方法的自定义文件对象上,该方法不像普通 Perl 文件对象那样处理换行符。
以下是解决方法。检查表示文件结束的未定义值读取:
while (defined(my $line = <FILE>))
print $line;
foreach
循环顺便没有这个问题,即使效率低下也是正确的。
【讨论】:
不! Perl 将“while (j_random_hacker 在 cmets 中向this answer 提到了这一点,但实际上并没有将其放在自己的答案中,尽管这是另一个值得一提的区别。
区别在于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
。
【讨论】:
【参考方案7】:这是一个示例,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 编程生涯的早期就很难学会这一点。
【讨论】:
【参考方案8】:foreach 循环比 while(基于条件的)更快。
【讨论】:
foreach 也是基于条件的。它的条件是它已经完成了对列表的工作。以上是关于使用 foreach 或在 Perl 中迭代文件有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章
在实体中查找对象和加载实体、Foreach 或在哪里使用 LinQ ON C# 的最佳性能?
我可以在 VC++ 6.0 编译的应用程序中嵌入 Strawberry Perl 或在编译器不匹配的情况下使用 Inline::C 吗?