【问题标题】:How can I quickly parse large (>10GB) files?如何快速解析大 (>10GB) 文件?
【发布时间】:2010-12-27 12:04:00
【问题描述】:

我必须处理格式为 10-20GB 的文本文件: 字段1 字段2 字段3 字段4 字段5

我想将 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

【问题讨论】:

  • 当你说慢时,你能详细说明一下吗?可能提供一些您希望它完成的范围?解析 10GB 的文本是一项相当大的工作。
  • 请尽可能显示示例文件

标签: perl awk large-files


【解决方案1】:

使用 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;
}

【讨论】:

    【解决方案2】:

    这是 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 社区有一个约定,可以将它用于您实际上并不关心的变量。)如果在输入行解压成五个变量。如果您的输入文件有时可以在一行中有四个单词,或者六个或更多,您可以修改程序以不引发异常;将主循环更改为:

    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 次相比,这可能会节省时间。

    【讨论】:

    • steveha - 非常感谢 - 我从未尝试过 python,但效果很好 - 我仍然看到〜两个小时左右来处理这个文件,这有点类似于它所花费的时间哦。我想我可能不得不冒险并遵循 John Strohm 的回答,即尝试 C - 真的出于我的专业知识,但我会在圣诞节 1ch1g0 上试一试 - 谢谢 - 我已经走了因为我无法一次打开 26 个文件,所以多次浏览该文件 - awk 抛出错误;也许我应该尝试 gawk 或类似的。节日快乐。
    • steveha - 更新 - 我说得太早了 - 你的 python 解决方案实际上在大约 30 分钟内运行 - 这对我来说很好,非常感谢帮助。
    • 我很高兴能够提供帮助。我用一些额外的信息更新了我的答案;我希望您会发现附加信息有用。祝你的项目好运,节日快乐!
    • 这个程序是否产生了正确的输出?它似乎与 awk 中的原始提案有所不同。
    • 嗯...我从顶部的文字开始工作,解释问题。看着 AWK 代码,我现在很困惑。那 for 循环在做什么?为什么要打印额外的数字?好吧,见鬼,我将编辑我的答案并提供与 AWK 匹配的代码。
    【解决方案3】:

    你知道它为什么慢吗?这是因为您正在使用外壳 for 循环处理该大文件 25 次。!!

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

    【讨论】:

    • 您的输出文件规范中有一个无与伦比的报价。
    • 此外,它会为每一行增加pat,因此在第 26 行之后停止执行任何操作。您需要嵌套 for loops
    • 感谢收看。至于您的第二条评论,我将等待 OP 在进行更改之前显示他的输入文件。 pat 变量可以使用文件中表示下一次迭代的特定模式递增。
    • 你从未定义过'pat'。我想你想要print i &gt;&gt; "my_out_file_"$4".query"
    【解决方案4】:

    有时候 awk 不是答案。

    有时脚本语言不是解决方案,当你硬着头皮拖下你的 K&R 副本并破解一些 C 代码时,你会更好。

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

    【讨论】:

    • -1:当涉及大 I/O 时,C 没有帮助。他宁愿研究算法(例如,读取文件一次,而不是 25 次),而不是切换语言!
    【解决方案5】:

    听起来你已经开始了,但我只想提到内存映射 I/O 在处理巨大文件时有很大的帮助。曾经有一段时间我不得不用 Visual Basic 5 解析一个 .5GB 的二进制文件(是的)... 导入 CreateFileMapping API 让我能够在分钟。而且只用了半个小时左右就实现了。

    这是一个描述 Microsoft 平台上的 API 的链接,尽管我确信 MMIO 应该在几乎任何平台上:MSDN

    祝你好运!

    【讨论】:

      【解决方案6】:

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

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

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

      后面写的时候可以print {$fh{$pat}} $tx{$base};

      【讨论】:

        猜你喜欢
        • 2018-12-09
        • 1970-01-01
        • 1970-01-01
        • 2020-06-15
        • 1970-01-01
        • 2020-10-10
        • 1970-01-01
        • 2019-11-24
        • 2017-09-13
        相关资源
        最近更新 更多