在Perl中,如何在不耗尽内存的情况下将行合并到一个大的、未排序的文件中?

在Perl中,如何在不耗尽内存的情况下将行合并到一个大的、未排序的文件中?,perl,memory,file,hash,Perl,Memory,File,Hash,我有一个非常大的以列分隔的文件,它来自一个数据库报告,如下所示: field1,field2,field3,metricA,value1 field1,field2,field3,metricB,value2 field1,field2,field3,value1,value2 我希望新文件具有如下组合行,以便它看起来像这样: field1,field2,field3,metricA,value1 field1,field2,field3,metricB,value2 field1,fie

我有一个非常大的以列分隔的文件,它来自一个数据库报告,如下所示:

field1,field2,field3,metricA,value1
field1,field2,field3,metricB,value2
field1,field2,field3,value1,value2
我希望新文件具有如下组合行,以便它看起来像这样:

field1,field2,field3,metricA,value1
field1,field2,field3,metricB,value2
field1,field2,field3,value1,value2
我可以用散列来做这件事。在本例中,前三个字段是键,我将value1和value按一定顺序组合为值。读入文件后,我只需将哈希表的键和值打印到另一个文件中。很好

然而,我有一些担心,因为我的文件将非常大。每个文件大约8 GB

有没有更有效的方法?我不是在考虑速度,而是在考虑内存占用。我担心这个进程可能会因为内存问题而死亡。我只是在一个可行的解决方案上画了一个空白,但最终不会把所有东西都塞进一个非常大的散列


为了完全公开,我在Windows上使用ActiveState Perl。

将另一个直接从数据库导出到新文件中,而不是重新处理已经输出的文件,这不是更好吗。如果这是一个选项,那么我会走这条路。

如果您的行按键排序,或者由于其他原因,字段1、字段2、字段3的相等值相邻,那么状态机将快得多。只要通读这些行,如果字段与前一行相同,则发出这两个值


否则,至少可以利用正好有两个值的事实,在找到第二个值时从哈希中删除键——这将大大限制内存使用。

如果您有其他类似Unix的工具可用(例如通过cygwin)然后,您可以使用sort命令预先对文件进行排序(该命令可以处理大型文件)。或者,您可以让数据库输出排序后的格式


一旦文件被排序,进行这种合并就很容易了——一次迭代一行,将最后一行和下一行保留在内存中,并在键更改时输出

你可以用它试试。它让我想起了一种可以在程序逻辑中使用的大型机。我用它做的很好

如果您认为这些数据无法存储在内存中,您可以随时调整 将哈希值添加到磁盘数据库:

use BerkeleyDB;
tie my %data, 'BerkeleyDB::Hash', -Filename => 'data';

while(my $line = <>){
    chomp $line;
    my @columns = split /,/, $line; # or use Text::CSV_XS to parse this correctly

    my $key = join ',', @columns[0..2];
    my $a_key = "$key:metric_a";
    my $b_key = "$key:metric_b";

    if($columns[3] eq 'A'){
        $data{$a_key} = $columns[4];
    }
    elsif($columns[3] eq 'B'){
        $data{$b_key} = $columns[4];
    }

    if(exists $data{$a_key} && exists $data{$b_key}){
        my ($a, $b) = map { $data{$_} } ($a_key, $b_key);
        print "$key,$a,$b\n";
        # optionally delete the data here, if you don't plan to reuse the database
    }
}
使用BerkeleyDB;
绑定我的%data,'BerkeleyDB::Hash',-Filename=>data';
while(我的$line=){
chomp$行;
my@columns=split/,/,$line;#或使用Text::csvxs正确解析此内容
我的$key=join',',@columns[0..2];
my$a_key=“$key:metric_a”;
my$b_key=“$key:metric_b”;
如果($columns[3]eq'A'){
$data{$a_key}=$columns[4];
}
elsif($columns[3]等式'B'){
$data{$b_key}=$columns[4];
}
if(exists$data{$a_key}&&exists$data{$b_key}){
my($a,$b)=映射{$data{${}($a_键,$b_键);
打印“$key,$a,$b\n”;
#如果不打算重用数据库,可以选择在此处删除数据
}
}

我很想这样做,但我无法直接访问数据库。那么如何获取第一个文件呢?我通过数据库所有者提供的http接口获取文件。我指定我想要的指标,并在每一行上放置一个指标。我希望它只是在输出的结果中添加列,但这不是它的本意。听起来对数据库所有者来说更像是一项工作,而不是对您来说。在他这一方做这件事会非常容易。这不是让我做第一个选择的方式。但我认为,一旦确定该条目已被访问两次,我将从哈希表中删除该条目。似乎是最好的选择。您可以始终使用外部排序程序对其进行排序,或者按照Axeman的建议使用sort::external。如果一对匹配的行可以在文件中相隔很远的地方出现,那么我几乎可以保证这比在这样大小的文件上使用哈希更快。哈希方法可能是n^2(在O(n)数据结构中查找n项),而排序可能是n log n,但另一方面,如果哈希适合内存,排序可能涉及更多的磁盘I/O。很难说哪个更快。如果列表中的对相距很远,则哈希可能无法放入内存。最糟糕的情况是,它们平均被文件的一半分隔开,但平均来说,它们之间的分隔可能要小得多。@Joel:理论上,对于O(n)总体而言,每次查找的哈希值为O(1)——问题是,对于一个8Gb的文件,这将检查典型PC上的RAM,除非你运气好,每一条线都是一条接一条地出现的。我投票赞成这一点,因为它是从另一个角度来解决问题的。换个角度思考。