如何快速解析大(> 10GB)文件?

时间:2009-12-17 01:56:48

标签: perl awk large-files

我必须处理格式大小为10-20GB的文本文件: field1 field2 field3 field4 field5

我想将field2的每一行数据解析成几个文件中的一个;推送到的文件是由field4中的值逐行确定的。 field2中有25个不同的可能值,因此数据可以解析为25个不同的文件。

我尝试过使用Perl(慢速)和awk(更快但速度更慢) - 有没有人对替代方法有任何建议或指示?

这里是我试图使用的awk代码;注意我不得不恢复通过大文件25次,因为我无法在awk中同时打开25个文件:

chromosomes=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25)
for chr in ${chromosomes[@]}
do

awk < my_in_file_here -v pat="$chr" '{if ($4 == pat) for (i = $2; i <= $2+52; i++) print i}' >> my_out_file_"$chr".query 

done

6 个答案:

答案 0 :(得分:15)

使用Perl,在初始化期间打开文件,然后将每行的输出与相应的文件匹配:

#! /usr/bin/perl

use warnings;
use strict;

my @values = (1..25);

my %fh;
foreach my $chr (@values) {
  my $path = "my_out_file_$chr.query";
  open my $fh, ">", $path
    or die "$0: open $path: $!";

  $fh{$chr} = $fh;
}

while (<>) {
  chomp;
  my($a,$b,$c,$d,$e) = split " ", $_, 5;

  print { $fh{$d} } "$_\n"
    for $b .. $b+52;
}

答案 1 :(得分:7)

你知道它为什么慢吗?因为你用外壳for循环处理那个大文件25次。!!

awk '
$4 <=25 {
    for (i = $2; i <= $2+52; i++){
        print i >> "my_out_file_"$4".query"
    }
}' bigfile

答案 2 :(得分:7)

这是Python的解决方案。我在一个我编写的小文件上测试了它。我认为即使是大文件,这也是可以接受的,因为大部分工作都是由Python内部的C代码完成的。我认为这是一个令人愉快且易于理解的程序;我更喜欢Python到Perl。

import sys

s_usage = """\
Usage: csplit <filename>
Splits input file by columns, writes column 2 to file based on chromosome from column 4."""

if len(sys.argv) != 2 or sys.argv[1] in ("-h", "--help", "/?"):

    sys.stderr.write(s_usage + "\n")
    sys.exit(1)


# replace these with the actual patterns, of course
lst_pat = [
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
    'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
    'u', 'v', 'w', 'x', 'y'
]


d = {}
for s_pat in lst_pat:
    # build a dictionary mapping each pattern to an open output file
    d[s_pat] = open("my_out_file_" + s_pat, "wt")

if False:
    # if the patterns are unsuitable for filenames (contain '*', '?', etc.) use this:
    for i, s_pat in enumerate(lst_pat):
        # build a dictionary mapping each pattern to an output file
        d[s_pat] = open("my_out_file_" + str(i), "wt")

for line in open(sys.argv[1]):
    # split a line into words, and unpack into variables.
    # use '_' for a variable name to indicate data we don't care about.
    # s_data is the data we want, and s_pat is the pattern controlling the output
    _, s_data, _, s_pat, _ = line.split()
    # use s_pat to get to the file handle of the appropriate output file, and write data.
    d[s_pat].write(s_data + "\n")

# close all the output file handles.
for key in d:
    d[key].close()

编辑:这里有关于这个程序的更多信息,因为看起来你将会使用它。

所有错误处理都是隐含的。如果发生错误,Python将“引发异常”,这将终止处理。例如,如果其中一个文件无法打开,该程序将停止执行,Python将打印一个回溯,显示哪行代码导致异常。我可以用“try / except”块包装关键部分来捕捉错误,但是对于一个简单的程序,我没有看到任何意义。

这是微妙的,但有一个检查,看看输入文件的每一行是否正好有五个单词。当这段代码解包一行时,它会分为五个变量。 (变量名称“_”是一个合法的变量名称,但是Python社区中有一个约定将它用于您实际上并不关心的变量。)如果不存在五个单词,Python将引发异常。输入行解压缩成五个变量。如果您的输入文件有时可以在一行上有四个单词,或者六行或更多,您可以修改程序以不引发异常;将主循环更改为:

for line in open(sys.argv[1]):
    lst = line.split()
    d[lst[3]].write(lst[1] + "\n")

将行拆分为单词,然后将整个单词列表分配到单个变量lst中。所以这行代码并不关心行上有多少个单词。然后下一行索引到列表中以获取值。由于Python使用0开始索引列表,因此第二个单词为lst[1],第四个单词为lst[3]。只要列表中至少有四个单词,该行代码也不会引发异常。

当然,如果该行的第四个单词不在文件句柄的字典中,那么Python也会引发异常。这将停止处理。下面是一些如何使用“try / except”块来处理这个问题的示例代码:

for line in open(sys.argv[1]):
    lst = line.split()
    try:
        d[lst[3]].write(lst[1] + "\n")
    except KeyError:
        sys.stderr.write("Warning: illegal line seen: " + line)

祝你的项目好运。

编辑:@larelogio指出此代码与AWK代码不匹配。 AWK代码有一个额外的for循环,我不明白。这是Python代码做同样的事情:

for line in open(sys.argv[1]):
    lst = line.split()
    n = int(lst[1])
    for i in range(n, n+53):
        d[lst[3]].write(i + "\n")

这是另一种方法。这可能会快一些,但我没有测试过,所以我不确定。

for line in open(sys.argv[1]):
    lst = line.split()
    n = int(lst[1])
    s = "\n".join(str(i) for i in range(n, n+53))
    d[lst[3]].write(s + "\n")

这将构建一个包含要写入的所有数字的字符串,然后将它们写入一个块中。与调用.write() 53次相比,这可以节省时间。

答案 3 :(得分:1)

有时候awk不是答案。

有些时候,脚本语言不是答案,当你最好咬掉子弹并拖下你的K&amp; R副本并攻击一些C代码时。

如果您的操作系统使用并发进程和进程间通信实现管道,而不是大临时文件,那么您可以做的是编写一个重新格式化该行的awk脚本,将选择器字段放在该行的开头。使用scanf()可以轻松读取的格式的行,编写一个C程序,打开25个文件并在它们之间分配行,并将awk脚本输出管道输出到C程序中。

答案 4 :(得分:1)

听起来好像你正在路上,但我只是想提一下内存映射I / O,这对于处理巨大的文件来说是一个巨大的帮助。曾经有一段时间我不得不使用Visual Basic 5解析.5GB二进制文件(是的)...导入CreateFileMapping API允许我解析文件(并创建几个“人类可读的”文件)在分钟。实施它只需要半个小时左右。

这是一个描述Microsoft平台上的API的链接,但我确信MMIO应该适用于任何平台:MSDN

祝你好运!

答案 5 :(得分:0)

有一些预先计算可能会有所帮助。

例如,您可以预先计算field2的每个值的输出。承认他们像field4一样25:

my %tx = map {my $tx=''; for my $tx1 ($_ .. $_+52) {$tx.="$tx1\n"}; $_=>$tx} (1..25);

在写作时,您可以print {$fh{$pat}} $tx{$base};