如何使用Perl更有效地处理大型文本文件

时间:2015-07-22 08:08:33

标签: arrays perl

问题:

我需要搜索一个巨大的文本文件(包含大约150万行数据),提取那些匹配唯一ID的行。我已将我的唯一ID存储在一个数组中,并且每个数组元素遍历整个文件一次。

虽然这种方法适用于小型阵列,但如果阵列非常大,那么由于需要执行大量操作,因此会大大减慢我的程序速度。

我的数组最多可以包含以下形式的10,000个唯一标识符:

DC888U1
DC888U2
DC888U3 
... 
...

我的数据文件中的行总是以唯一标识符开头。

DC888U1 Apples 0.99 75
DC888U2 Oranges 0.75 1002
DC888U3 Bread 1.35 100
... ... ... ...
... ... ... ...

我的代码如下:

#array containing identifiers
open (IDENTIFIERS "< keywords.txt") or die "Cannot open file: $!";
    chomp(my @keywords = <IDENTIFIERS>);
close (IDENTIFIERS);

#iterate through the array element by element
foreach my $element (@keywords) {
    open (FH "< inventory.txt") or die "cannot open file: $!";
    while (<FH>) {
        if ($_ =~ /^\Q$element\E/) {
            print $_;
        }
    }
close (FH);
}

我看过Tie :: File,看看是否可以加快我的处理速度,但没有运气。我想知道有没有办法可以缓存已打印的行,这样当我下次浏览文件时,每次搜索的数据量都会减少。

有吗?

3 个答案:

答案 0 :(得分:4)

关键是将你的O(N * M)代码转换为O(N + M):

use strict;
use warnings;
use v5.10;  # For autodie
use autodie;

die <<ERROR unless @ARGV > 1;
Identifiers file missing.
Usage: $0 identifiers_file [ inventory_file ]
ERROR

my $keywords_re = do {
    my $keywords_file = shift;
    open my $fh, '<', $keywords_file;
    my @keywords = <$fh>;
    chomp @keywords;
    my $re = join '|', map quotemeta, @keywords;
    qr/$re/;
};

while (<>) { print if /^$keywords_re\s/ }

如果您确定自己的关键字不能包含受Sinan Ünür solution启发的空白,则可以选择

my %keywords;
{
    my $keywords_file = shift;
    open my $fh, '<', $keywords_file;
    @keywords{ map s/\s//gr, <$fh> } = (); #/ make syntax highlight happy
};

while (<>) { print if /^(\S+)/ and exists $keywords{$1} }

答案 1 :(得分:3)

对于大多数常见的线路长度,150万行数据并不是很大。如果每条线都是1K,那么即使在我十年前的笔记本电脑上,你也有1.5GB的数据可以很好地存储在内存中。

您的问题是由于您为每个标识符重复处理文件。

因此,如果您有10,000个标识符,并且处理文件需要一秒钟,那么您的过程仍需要3个小时。如果处理文件需要一分钟,那么您的方法将需要7天。

将10,000个标识符放在哈希中作为键。然后,当您遍历文件时,捕获每行上的非空格字符的初始序列,检查它是否是哈希中的键;打印如果是。

未测试:

 my %keywords = map { $_ => undef } @keywords;
 while (my $line = <$in>) {
     my ($id) = ($line =~ /^(\S+)/);
     if (exists $keywords{$id}) {
          print $line;
     }
 }

答案 2 :(得分:1)

您要求进行优化,这在很大程度上取决于具体情况。

如果您对文件进行了排序(使用&#34;排序&#34;实际上意味着&#34;根据您自己的标准排序&#34;),您可以决定浪费一些磁盘空间并创建一个包含的新文件填充的相同线条具有相同的长度。

然后,您对该文件使用二进制搜索,以获取您要查找的标识符中至少有一个出现的行号(这就是为什么您需要相同的行长度,< em> seek 在文件上将无法正常工作,否则)。

如果标识符在文件中是唯一的,那么您就完成了。 如果他们不是,你只需向上移动一行,直到标识符发生变化并向下移动一行,直到标识符改变并且你有间隔。

再说一遍:这只有在文件排序且行长度相同的情况下才有效,但如果是这样的话,那么你将会看到巨大的速度提升。我知道,因为我自己这样做是为了搜索200Mb +文本文件:)