【问题标题】:Perl reading only specific gz file linesPerl 只读取特定的 gz 文件行
【发布时间】:2021-06-25 06:12:16
【问题描述】:

我正在尝试制作一个解析脚本来解析一个巨大的文本文件(超过 200 万行),该文件是 gunzip 压缩的。我只想解析文本文件中的一系列行。到目前为止,我已经使用 zgrep -n 来查找提到我知道将开始和结束我感兴趣的文件部分的字符串的两行。

在我的测试用例文件中,我只想阅读第 123080 到 139361 行。我发现 Tie::File 可以使用它返回的数组对象来访问文件行,但不幸的是,这对枪不起作用我正在使用的压缩文件。

枪压缩文件是否有类似以下内容?

use Tie::File
tie @fileLinesArray, 'Tie::File', "hugeFile.txt.gz"
my $startLine = 123080;

my $endLine = 139361;    
my $lineCount = $startLine;
while ($lineCount <= $endLine){
    my $line = @fileLinesArray[$lineCount]
    blah blah...
}

【问题讨论】:

    标签: perl gzip tie


    【解决方案1】:

    使用核心模块IO::Uncompress::Gunzip

    use IO::Uncompress::Gunzip;
    
    my $z = IO::Uncompress::Gunzip->new('file.gz');
    $z->getline for 1 .. $start_line - 1;
    for ($start_line .. $end_line) {
        my $line = $z->getline;
        ...
    }
    

    Tie::File 在处理大文件时变得非常缓慢且内存占用很大。

    【讨论】:

    • 如果行是固定长度的,可以seek到正确的记录($z-&gt;seek($record_length * $start_line, 0)
    • @Anfoni, Re "有没有办法直接跳到起始行而不必为前几千行执行 getline?",文本文件是未编入索引,这意味着无法知道行的开始位置。甚至 Tie::File 也需要读取所有行,直到您请求的行。 (Tie::File 也比不使用 Tie::File 的时间长 30 倍。切勿使用 Tie::File)
    • @choroba,Tie::File 在处理大文件时变得非常缓慢且内存占用期间。它将遇到的每一行的索引存储在可配置大小的缓冲区之上,无论文件大小,它都有疯狂的计算开销。
    • 也可以for (1 .. $end_line) { my $line = $z-&gt;getline; next if $_ &lt; $start_line; ... }.
    • seek 的第一个参数是字节数,而不是行数。
    【解决方案2】:

    Tie::File 对于大文件来说是个坏主意,因为它需要一次将整个文件存储在内存中。对于压缩文件来说,这也是一个不切实际的想法,如果不是不可能的话。相反,您将希望对数据的输入流进行操作。如果您要修改数据,则输出流到数据的新副本。 Perl 通过PerlIO::gzip 层对gzip 压缩提供了很好的支持,但您也可以通过一两个gzip 进程来管道数据。

    # I/O stream initialization
    use PerlIO::gzip;
    open my $input, "<:gzip", "data.gz";
    open my $output. ">:gzip", "data.new.gz";    # if $output is needed
    
    # I/O stream initialization without PerlIO::gzip
    open my $input, "gzip -d data.gz |";
    open my $output, "| gzip -c > data.new.gz";
    

    一旦设置了输入(和可选的输出)流,您就可以在它们上使用 Perl 的 I/O 工具,就像任何其他文件句柄一样。

    # copy first $startLine lines unedited
    while (<$input>) {
        print $output $_;
        last if $. >= $startLine;
    }
    
    while (my $line = <$input>) {
        # blah blah blah
        # manipulate $line
        print $output $line;
        last if $. >= $endLine;
    }
    
    print $output <$input>; # write remaining input to output stream
    close $input;
    close $output;
    

    【讨论】:

    • Re "Tie::File 对于大文件来说是个坏主意,因为它需要一次将整个文件存储在内存中",这不是真的。它仅将文件的可配置大小部分保留在内存中。但它确实将遇到的每一行的偏移量存储在内存中,这很容易占用比文件本身更多的内存。所以 Tie::File 不仅比其他替代品慢得多——我在测试中看到它慢了 30 倍——它还使用更多的内存,而不是简单地将整个文件加载到内存中。这是一个糟糕的模块,应该避免。
    【解决方案3】:

    您写道:“在我的测试用例文件中,我只想阅读第 123080 到 139361 行”。

    这也可以在 shell 中完成:

    zcat file | tail -n +123080 | head -16282
    

    或通过:

    my $file = 'the_file.gz';
    my($from,$to) = (123080,139361);
    my @lines = qx( zcat $file | tail -n +$from | head -@{[-$from+$to+1]});
    

    这可能比普通的单核纯 perl 解决方案更快,因为 qx 内部的 zcattailhead 将变成三个进程,而 perl 是第四个。并且所有四个都可以自己获得一个单独的 cpu 内核。您可能想用不同的行号测试速度。

    【讨论】:

    • 感谢您的提示!我一定会试一试,然后告诉你结果!
    猜你喜欢
    • 2011-07-20
    • 2014-07-25
    • 2020-01-03
    • 2014-07-29
    • 2012-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-12
    相关资源
    最近更新 更多