我在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 MB的实际内存(RSS
列)来存储我2.4 MB的/usr/share/dict/words文件的内容。而while
循环每次仅存储一行,只消耗70 KB用于行缓冲。
while
),<FILE>
按顺序返回每一行。foreach
),<FILE>
返回由文件中每一行组成的列表。while
结构。while (<FILE>) { … }
在 foreach 循环中,会先将
$_
局部化,而不会对其进行修改。而 trample on 就是对$_
进行修改。毫无疑问,这是最重要的行为差异!
$_ = 42; foreach (@some_list) { ... }
中,由于Perl在这种情况下自动定位了 $_
,所以 $_
的值为42。但是在 $_ = 42; while (<FILE>) { ... }
之后,$_
是从 FILE
中读取的最后一行文本(通常情况下是undef
,表示已经读完整个文件)。这很让人恼火,因为foreach
的行为更安全/更易维护,但使用foreach
读取文件意味着要先将整个文件读入内存,如果您有一个大文件,逐行处理就足够了,这将非常浪费内存! - j_random_hacker除了之前的回答外,使用while
的另一个好处是您可以使用$.
变量。这是最后一个访问的文件句柄的当前行号(请参阅perldoc perlvar
)。
while ( my $line = <FILE> ) {
if ( $line =~ /some_target/ ) {
print "Found some_target at line $.\n";
}
}
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 在这个答案的评论中提到了这一点,但实际上并没有单独回答,尽管这是另一个值得注意的区别。
区别在于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
。更新:评论中的 j random hacker 指出,当从文件句柄读取时,Perl 会特殊处理 while 循环中的假测试。我已经验证了读取 false 值不会终止循环——至少在现代 Perl 中是这样的。抱歉给大家带来困扰。写了 15 年 Perl 的我还是个新手。 ;)
以上所有人都是正确的:使用 while
循环更加节省内存并且能够给你更多的控制。
关于 while
循环的一个有趣的事情是,它会在读取 false 时退出。通常这将是文件结尾,但如果它返回一个空字符串或 0 呢?糟糕!你的程序就会过早退出。如果文件的最后一行没有换行符,这可能会发生在任何文件句柄上。如果自定义文件对象具有不像常规 Perl 文件对象那样处理换行符的读取方法,也可能会发生这种情况。
以下是如何解决此问题。检查是否读取了未定义的值,这表示已到达文件结尾:
while (defined(my $line = <FILE>)) {
print $line;
}
< p > 顺便说一下,< code > foreach 循环没有这个问题,即使效率低下也是正确的。
这里有一个例子,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中也会有解决这个问题的方法(比如读入数组),但while绝对提供了一个非常直接的解决方案。
第二个例子是当你需要解析一台机器上的大型文件(比如3GB),而该机器只有2GB RAM时。foreach
将会耗尽内存并崩溃。我在我的perl编程生涯早期就是通过这种艰难的方式学习到了这一点。
foreach循环比while循环(基于条件)更快。