【问题标题】:efficiently reading N lines at a time from an input file in perl从 perl 中的输入文件一次有效地读取 N 行
【发布时间】:2014-08-01 03:55:07
【问题描述】:

我的输入数据文件的结构使得以N 行的块而不是一次读取一行数据更符合逻辑。当然,我可以使用像

这样简单的东西
my @lines=();
while(!eof($FH)) {
  for(my $i=0;$i<$N;$i++)
   $lines[$i]=<FH>;
   chomp();
  }
  # proceed with analysis of N-size block ##
}

由于输入文件非常大(GB),但是我想知道是否有比for 循环更有效的解决方案。例如,我找到了另一个解决方案online,它使用了map 函数,尽管当我尝试在我的脚本中实现它时,它会导致错误("my" variable @lines masks earlier declaration in same statement):

while(( my @lines = map $_ = <>, 1 .. 4 )[0]) {
  print @lines;
  print "\n";
}

诚然,我不明白这段代码 while 块中 [0] 的意义,另一个解决方案建议使用 [-1] 代替。

考虑到操作的 I/O 密集度,我想知道解决这个问题(在 Perl 编程语言范围内)的计算效率最高的解决方案是什么。

【问题讨论】:

    标签: perl


    【解决方案1】:

    为简单起见,我可能会建议从主 while 循环中读取数据并添加到缓冲区:

    my @buffer;
    
    while (<$FH>) {
        push @buffer, $_;
    
        if (@buffer == $N || eof) {
            print @buffer;
            @buffer = ();
        }
    }
    

    从算法上讲,我不希望任何特定方法比其他任何方法都快得多。您可以尝试使用其他从文件句柄读取的方法,但最终,我不希望找到任何重大的速度改进。

    【讨论】:

    • 我想我更喜欢我的:)
    • 我也喜欢你的,因为我喜欢避免循环外的状态变量。不必在 10 行代码中包含 5 次变量名只是“感觉”更整洁:)。但是,对于新程序员来说,状态变量可能是他们应该学习的第一个工具,因为它们的使用通常更易于阅读和维护。
    • 我很早就发现的一件事是数据本身可以用作状态标志。例如,如果您需要区别对待第一个条目,if (@buffer) { .. } 可以正常工作。比$started 标志好得多
    【解决方案2】:

    到目前为止,任何文件 IO 中最慢的瓶颈是磁盘本身。 Perl 以任意大的块读取文件并在它们中搜索换行符,以便它可以一次将数据传递给您一行。这意味着任何一次读取多行的方案只需要一小部分时间来从磁盘读取下一个块。因此,像往常一样,最普遍的标准是代码的可读性。

    一旦我开始编码,我就会明白为什么最明显的解决方案是map。不幸的是,它看起来像这样

    use strict;
    use warnings;
    
    use Data::Dump;
    
    use constant N => 4;
    
    while (my @block = grep defined, map { scalar <DATA> } 1 .. N) {
      dd \@block;
    }
    
    __DATA__
    1
    2
    3
    4
    5
    6
    7
    8
    9
    

    输出

    ["1\n", "2\n", "3\n", "4\n"]
    ["5\n", "6\n", "7\n", "8\n"]
    ["9\n"]
    

    但它可以写得更干净。到目前为止,我最喜欢这个

    use strict;
    use warnings;
    
    use Data::Dump;
    
    use constant N => 4;
    
    until (eof DATA) {
      my ($rec, @block);
      push @block, $rec while @block < N and $rec = <DATA>;
      dd \@block;
    }
    
    __DATA__
    1
    2
    3
    4
    5
    6
    7
    8
    9
    

    具有相同的输出。

    我正在考虑类似的事情

    while (do { ... }) {
       dd \@block;
    }
    

    但我还没到呢!

    【讨论】:

    • 从 DATA 切换到 ARGV/STDIN 成为一个问题:您需要连续两次获得 EOF 才能结束循环,但情况并非总是如此。
    • 第一个sn-p:a&lt;enter&gt;b&lt;enter&gt;c&lt;enter&gt;^De&lt;enter&gt;f&lt;enter&gt;g&lt;enter&gt;h&lt;enter&gt;^D^D^D^D;第二个 sn-p 好一点:a&lt;enter&gt;b&lt;enter&gt;c&lt;enter&gt;^De&lt;enter&gt;f&lt;enter&gt;g&lt;enter&gt;h&lt;enter&gt;^D。在这两种情况下,输出都是["a\n", "b\n", "c\n"]["d\n", "e\n", "f\n", "g\n"] 而不是["a\n", "b\n", "c\n"]
    • @ikegami:但eof DATA 不消耗记录。这需要readline 和后续的eof 返回false,这很好
    • 感谢您指出 perl 从磁盘大量读取数据,然后根据需要打包读取。我不理解这一点,并认为 I/O 为每一行返回到磁盘。
    • eof FH 使用 EOF 通知。虽然这对DATA 来说不是问题,但对于其他句柄来说可能是个问题(正如我所演示的)。
    猜你喜欢
    • 2022-08-12
    • 2020-02-25
    • 2011-01-30
    • 2011-01-30
    • 1970-01-01
    • 2011-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多