使用哈希DataStructure比较perl中两个巨大的csv文件的最佳方法是什么?

时间:2014-05-10 11:28:19

标签: perl hash

string.txt包含必须在第二个文件(input.csv)中搜索的字符串(此数据是唯一的) 当匹配时,它必须将输出重定向到文件。

现在我已经创建了执行此操作的代码,但是当我运行此脚本时,会断言" 内存不足"

有人可以让我知道以最快速度和绕过" 内存不足"错误。

我相信它是由于文件的大小,以及我在那里构建的复杂哈希数据结构。

  

记录string.txt的数量= 5611273(~100 MB)

     

记录input.csv = 65261242(~2.4 GB)

的计数

以下是示例文件内容

string.txt

alpha
beta
delta
gamma
bob
tom
jerry

input.csv

alpha|a1|b2|c3
delta|a2|b2|c3
beta|a1|b2|c3
gamma|a1|b2|c3
omega|a1|b2|c3
alpha|a1|b2|c3
delta|a2|b2|c3

示例哈希数据结构

   'gamma' => {
                       '4' => [
                                'a1',
                                'b2',
                                'c3'
                              ]
                     },
          'delta' => {
                       '7' => [
                                'a2',
                                'b2',
                                'c3'
                              ],
                       '2' => [
                                'a2',
                                'b2',
                                'c3'
                              ]
                     },

代码

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my %hash;
my $key;
local $"="|"; #"
my $count=1;

open(my $INPUT_FH,'<','/home/chidori/input.csv') or die "Can't open the file $!\n";

while(my $line = <$INPUT_FH>) {
  chomp($line);
  my @line = split (/\|/,$line);
  my $key = shift @line;
  push (@{$hash{$key}{$count}},@line);
  $count++;
}

#print Dumper (\%hash);
close($INPUT_FH);

open(my $STRING_FH,'<','/home/chidori/string.txt') or die "Can't open the file $!\n";

while( my $search_string = <$STRING_FH> ) {
  chomp($search_string);
  if (exists $hash{$search_string} ) {
    foreach my $k( keys %{$hash{$search_string}}) {
      my @line_to_print;
      push (@line_to_print,$search_string);
      push (@line_to_print,@{$hash{$search_string}{$k}});
      print "@line_to_print\n";  #Temporarily printing it to STDOUT. But need to redirect it to a outfile
    }
  }
}

close($STRING_FH)

3 个答案:

答案 0 :(得分:1)

您可以按第一个值对csv文件进行排序,然后将string.txt读入内存,并在while循环中处理csv文件。

答案 1 :(得分:1)

Chidori,有几种可能的解决方案,其中一种试图接近你已有的解决方案。另一个人会将整个事物视为数据库。

所以,这里有一些关于你的策略的评论:

  • 此时,您尝试从2.4GB文件构建一个庞大的数据结构,之后,您将读取较小的数据结构,以查看是否存在匹配项。你可以反过来做,请阅读&#39; string.txt&#39;进入哈希,键作为文件的每一行和任何值(undef?)。

  • 如果您真的想将输入文件用作CSV,请使用&#39; |&#39;作为分隔符,尽可能use Text::CSV。如果它是一个普通的ASCII文件,那么在&#39; |&#39;上进行拆分。适当而且更快。

  • 由于您要打印@line_to_print,因此不需要首先创建该数组,将其推送到该数组,然后打印出元素。 print提供了一个所谓的列表上下文&#39;因此print $search_string, @{$hash->{$search_string}{$k}}就足够了,再加快一点。

  • Perl在读取文件时确实有内置的行计数器

我希望这能给出足够的提示,说明如何在有限的内存限制下使其工作,甚至加快速度。因此,不需要首先对文件进行排序,哈希机制本身具有超快速查找方法。

  

这是一个下雨天,如果你需要,可以做一些Perl编码的好时机   更多帮助。

答案 2 :(得分:1)

保持working set足够小是在内存资源有限的机器上处理大量数据文件的关键。

在原帖中描述的问题中,将input.csv的所有内容保留在内存中,换句话说,使用该文件的所有内容作为工作集,将得到一个工作集太大而不适合记忆。这就是“内存不足”的原因。错误。要解决这个问题,我们需要减小该工作集的大小。

因为我们只对input.csv的第一个字段位于string.txt的行string.txt感兴趣,所以我们可以使用input.csv作为过滤器来过滤string.txt行1}}我们不感兴趣。

如果过滤的结果仍然太大,我们可以将其拆分为多个文件,逐个处理,然后合并这些结果以获得最终结果。

  1. 阅读while (readline) { discard current line unless it in filter first_char = extract first character of current line store current line to file named first_char } 以创建过滤器

  2. 拆分输入数据文件

    string.txt

    如果我们可以在 ID | VALUE ---------- id1 | val1 id2 | val2 中为每个实体打开一个文件,那会简单得多。我们不能这样做,因为文件描述符也是进程的有限资源。

    如果在此步骤之后某些数据文件仍然太大而无法容纳在内存中,我们需要使用类似的方法递归地拆分它们。

  3. 处理每个较小的数据文件,并为其生成结果文件

  4. 将所有这些结果文件合并到最终结果文件

  5. 您可以使用数据库代替手动拆分输入数据文件,让数据库管理系统为您完成所有这些工作。要解决原始帖子中描述的问题,您可以在数据库中创建一个表

    ID

    此处input.csvVALUE行的第一个字段,string.txt是相应行的其余部分。

    如果您想使用ID作为过滤器,可以先将所有文件的行(VALUE,空input.csv)插入该表。

    之后,您可以逐行处理输入数据文件 while (readline) { id = extract first field of current line search for record with `ID` equals id in table if that record exists, update it with a new value; else insert a new record }

        select all record from that table
        write them to a file one by one
    

    完成数据导入后,您可以获得最终结果

    {{1}}