【问题标题】:Perl6: large gzipped files read line by linePerl6:逐行读取大型压缩文件
【发布时间】:2019-02-21 18:21:34
【问题描述】:

我正在尝试在 Perl6 中逐行读取 gz 文件,但是我被阻止了:

  1. How to read gz file line by line in Perl6 然而,这种将所有内容读入:out 的方法使用的 RAM 太多,无法使用,除非是非常小的文件。

  2. 我不明白如何使用 Perl6 的 Compress::Zlib 逐行获取所有内容,尽管我在他们的 github https://github.com/retupmoca/P6-Compress-Zlib/issues/17 上打开了一个问题

  3. 我正在尝试 Perl5 的 Compress::Zlib 来翻译这段代码,它在 Perl5 中完美运行:

use Compress::Zlib;
my $file = "data.txt.gz";
my $gz = gzopen($file, "rb") or die "Error reading $file: $gzerrno";

while ($gz->gzreadline($_) > 0) {
    # Process the line read in $_
}

die "Error reading $file: $gzerrno" if $gzerrno != Z_STREAM_END ;
$gz->gzclose() ;

在 Perl6 中使用 Inline::Perl5 处理类似的事情:

use Compress::Zlib:from<Perl5>;
my $file = 'chrMT.1.vcf.gz';
my $gz = Compress::Zlib::new(gzopen($file, 'r');
while ($gz.gzreadline($_) > 0) {
  print $_;
}
$gz.gzclose();

但我看不到如何翻译这个:(

  1. 我对 Lib::Archive 示例 https://github.com/frithnanth/perl6-Archive-Libarchive/blob/master/examples/readfile.p6 感到困惑,我不知道如何在这里获得类似于第 3 项的内容

  2. 应该有类似

for $file.IO.lines(gz) -&gt; $line { 或 Perl6 中类似的东西,如果它存在,我找不到它。

如何在 Perl6 中逐行读取大文件而不将所有内容都读入 RAM?

【问题讨论】:

  • Re: " using Inline::Perl5":对于$gz.gzreadline($_)的调用:似乎gzreadline试图通过修改返回从zip文件中读取的行它的输入参数$_(被视为输出参数,但不是真正的 Perl 5 引用变量),但该值不会返回到 Perl 6 脚本。
  • 关于 Perl 5 gzreadline 函数的更多信息:在 Perl 5 中,您可以修改不是引用的输入参数,更改将反映在调用者中。这是通过修改特殊@_ 数组变量中的条目来完成的。例如:sub quote { $_[0] = "'$_[0]'" } $str = "Hello"; quote($str) 将引用 $str,即使 $str 没有通过引用传递。我不确定Inline::Perl5 是否可以处理这类输出参数
  • 关于项目#4,只是为了澄清一下:底层库提供了一个archive_read_data_block(),它是通用的,必须适用于每种存档格式的每种文件。 read-file-content 方法在Buf 中返回整个文件。要逐行读取文件,应该使用Archive::Libarchive::Raw编写自己的方法,但我发现有更简单的方法可以做到这一点,如您的问题的答案所示。

标签: gzip raku


【解决方案1】:

更新现已测试,发现错误,现已修复。

解决方案 #2

use Compress::Zlib;

my $file   = "data.txt.gz" ;
my $handle = try open $file or die "Error reading $file: $!" ;
my $zwrap  = zwrap($handle, :gzip) ;

for $zwrap.lines {
    .print
}

CATCH { default { die "Error reading $file: $_" } }

$handle.close ;

我已经用一个压缩过的小文本文件对此进行了测试。

我对 gzip 等不太了解,但根据以下情况得出了这一点:

  • 认识P6;

  • 读取Compress::Zlib's README并选择zwrap例程;

  • 查看模块源码,特别是the zwrap routineour sub zwrap ($thing, :$zlib, :$deflate, :$gzip)的签名;

  • 和反复试验,主要是猜测我需要通过:gzip副词。


请评论我的代码是否适合您。我猜主要是它对于您拥有的大文件是否足够快。

解决方案 #5 的尝试失败

在解决方案 #2 工作的情况下,我希望能够只写:

use Compress::Zlib ;
.print for "data.txt.gz".&zwrap(:gzip).lines ;

但这失败了:

No such method 'eof' for invocant of type 'IO::Path'

这大概是因为这个模块是在 IO 类重组之前编写的。

这让我找到了@MattOates' IO::Handle like object with .lines ? issue。我注意到没有回应,我在https://github.com/MattOates?tab=repositories 上没有看到相关的回购。

【讨论】:

    【解决方案2】:

    我专注于您尝试的Inline::Perl5 解决方案。

    对于$gz.gzreadline($_) 的调用:似乎gzreadline 试图返回从压缩文件by modifying its input argument $_ 读取的行(被视为输出参数,但它不是真正的Perl 5 引用变量 [1]),但修改后的值不会返回到 Perl 6 脚本。

    这是一个可能的解决方法: 在当前目录中创建一个包装模块,例如./MyZlibWrapper.pm

    package MyZlibWrapper;
    use strict;
    use warnings;
    use Compress::Zlib ();
    use Exporter qw(import);
    
    our @EXPORT = qw(gzopen);
    our $VERSION = 0.01;
    
    sub gzopen {
        my ( $fn, $mode ) = @_;
        my $gz = Compress::Zlib::gzopen( $fn, $mode );
        my $self = {gz => $gz}; 
        return bless $self, __PACKAGE__;
    }
    
    sub gzreadline {
        my ( $self ) = @_;
        my $line = "";
        my $res = $self->{gz}->gzreadline($line);
        return [$res, $line];
    }
    
    sub gzclose {
        my ( $self ) = @_;
        $self->{gz}->gzclose();
    }    
    
    1;
    

    然后在这个包装模块上使用Inline::Perl5 而不是Compress::Zlib。例如 ./p.p6:

    use v6;
    use lib:from<Perl5> '.';
    use MyZlibWrapper:from<Perl5>;
    my $file = 'data.txt.gz';
    my $mode = 'rb';
    my $gz = gzopen($file, $mode);
    loop {
        my ($res, $line) = $gz.gzreadline();
        last if $res == 0;
        print $line;
    }
    $gz.gzclose();
    

    [1] 在 Perl 5 中,您可以修改不是引用的输入参数,并且更改将反映在调用者中。这是通过修改特殊@_ 数组变量中的条目来完成的。例如:sub quote { $_[0] = "'$_[0]'" } $str = "Hello"; quote($str) 将引用 $str,即使 $str 没有通过引用传递。

    【讨论】:

    • 您可以use lib:from&lt;Perl5&gt; '.'; 更改 Perl5 使用的路径。然后use MyZlibWrapper:from&lt;Perl5&gt;; 就可以了。
    猜你喜欢
    • 2022-01-06
    • 2012-07-14
    • 2015-07-01
    • 2019-05-29
    • 2019-09-03
    • 1970-01-01
    • 1970-01-01
    • 2015-06-04
    • 1970-01-01
    相关资源
    最近更新 更多