使用 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 首先本地化 $_)。这无疑是最重要的行为差异! @j_random_hacker 内存差异更为重要和实用。无论如何,您都不应该依赖 $_ 来处理任何显着距离的代码,因为很多东西都会践踏它。 foreach 使用列表还有另一个含义。如果文件句柄是一个保存另一个程序输出的管道,foreach 将等到管道关闭,因为只有这样它才能确定它将所有行读入数组,而while 只会阻塞直到\n(或更准确地说,输入记录分隔符)通过管道发送。使用while 允许您在一个程序完成运行之前处理它的输出。【参考方案2】:

在标量上下文中(即while&lt;FILE&gt; 依次返回每一行。

在列表上下文中(即foreach&lt;FILE&gt; 返回一个由文件中的每一行组成的列表。

您应该使用while 构造。

请参阅perlop - I/O Operators 了解更多信息。

编辑:j_random_hacker 说得对

while (<FILE>)  … 

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

【讨论】:

-1 直到您提到 while () 践踏 $_ 而 foreach 没有(foreach 首先本地化 $_)。这无疑是最重要的行为差异! 谢谢!这种不直观的差异是很多错误的根源。 @j_random_hacker 你能详细说明一下while循环“践踏”$_而foreach本地化$_是什么意思吗?以及我应该注意的注意事项?我是 perl 初学者,正在努力学习基础知识... @Alby:在代码$_ = 42; foreach (@some_list) ... 之后,$_ 是 42,因为在这种情况下 Perl 会自动本地化 $_。但是在$_ = 42; while (&lt;FILE&gt;) ... 之后,$_ 是从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 () ... ”的形式特例化为您建议的替换形式:“while (defined($_ = )) ”。因此,文件末尾仅包含“0”且没有 LF 字符的行将 not 被忽略。请参阅 perlop 中的“I/O 运算符”部分。 甜蜜!这是什么时候解决的? pod 中还有很多使用 while defined 语法的示例。 IIRC Perl 过去对 while() 的处理方式与 while() 不同。 AFAIK 从 Perl 5 开始就是这样,但我不知道。 Perl 对特殊情况下的形式很挑剔:例如"while ()"、"while ($_ = )" 和 "while (my $x = )" 得到特殊情况,但 "while ($_ = '' . )”没有。 (使用以“0”结尾且没有 LF 的文件进行测试。) 别担心感觉自己像个菜鸟...我从 1999 年就开始使用 Perling,一个月前了解到范围运算符是两个常量标量的特例! (例如“1 .. 10”):) 是的,有些 POD 文档已经过时了,这很糟糕,谷歌搜索也会出现一些不好的建议/解释。 我放弃了我给你的-1,但如果你提到“while ()”践踏$_ 而“foreach () " 本地化 $_,避免踩踏。这种不明显的行为差异会导致很多细微的错误。【参考方案6】:

j_random_hacker 在 cmets 中向this answer 提到了这一点,但实际上并没有将其放在自己的答案中,尽管这是另一个值得一提的区别。

区别在于while (&lt;FILE&gt;) 覆盖$_,而foreach(&lt;FILE&gt;) 将其本地化。那就是:

$_ = 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

将打印出&lt;FILE&gt;的最后一行。

然而,

$_ = 100;
foreach(<FILE>) 
    # $_ gets each line in turn
    # do something with the file

print $_;

将打印出100。要获得与 while(&lt;FILE&gt;) 相同的构造,您需要这样做:

$_ = 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# 的最佳性能?

如何跳过“foreach”循环的迭代?

Perl Foreach 通过多维数组?

perl之文本文件的嵌套循环

Perl 执行 DBI 循环执行

我可以在 VC++ 6.0 编译的应用程序中嵌入 Strawberry Perl 或在编译器不匹配的情况下使用 Inline::C 吗?