代码之家  ›  专栏  ›  技术社区  ›  Zsolt Botykai

SED优化(基于较小数据集的大文件修改)

  •  8
  • Zsolt Botykai  · 技术社区  · 15 年前

    我必须处理非常大的纯文本文件(超过10G字节,是的,我知道这取决于我们应该称之为大文件),行数非常长。

    我最近的任务涉及一些基于另一个文件数据的行编辑。

    数据文件(应该修改)包含1500000行,每行长度为800个字符。每行都是唯一的,并且只包含一个标识号,每个标识号都是唯一的)

    修改文件的长度为1800行,包含一个标识号,以及数据文件中应修改的金额和日期。

    我只是将修改文件(使用vim regex)转换为sed,但是效率非常低。

    假设我在数据文件中有这样一行:

    (some 500 character)id_number(some 300 character)
    

    我需要修改300个字符部分的数据。

    基于修改器文件,我提出了这样的SED行:

    /id_number/ s/^\(.\{650\}\).\{20\}/\1CHANGED_AMOUNT_AND_DATA/
    

    所以我有1800条这样的线路。

    但我知道,即使在一个非常快的服务器上,如果我

    sed -i.bak -f modifier.sed data.file
    

    它非常慢,因为它必须读取每一行的每一个模式x。

    没有更好的方法吗?

    注: 我不是程序员,从未(在学校)学过算法。 我可以在服务器上使用awk,sed,一个过时的Perl版本。

    6 回复  |  直到 9 年前
        1
  •  6
  •   MikeyB    15 年前

    我建议的方法(按希望的顺序)是将这些数据处理为:

    1. 一个数据库(即使是一个带有索引的基于sqlite的简单数据库,在10GB文件上的性能也比sed/awk要好得多)
    2. 包含固定记录长度的平面文件。
    3. 包含可变记录长度的平面文件。

    使用数据库可以处理所有那些减慢文本文件处理速度的小细节(查找您关心的记录、修改数据、将其存储回数据库)。在Perl中查找dbd::sqlite。

    如果你想坚持使用平面文件,你需要在大文件旁边手动维护一个索引,这样你就可以更容易地查找你需要操作的记录编号。或者,更好的是,也许你的身份证号码 你的记录号码?

    如果您有可变的记录长度,我建议您转换为固定的记录长度(因为只有您的ID是可变长度)。如果您不能这样做,可能任何现有的数据都不会在文件中移动?然后,您可以维护前面提到的索引,并根据需要添加新条目,不同的是,您现在可以指向文件中的绝对位置,而不是指向记录号的索引。

        2
  •  3
  •   yves Baumes    15 年前

    我建议您使用Perl编写一个程序(因为我不是一个SED/AWK专家,我不知道他们到底能做什么)。

    您的“算法”很简单:首先,您需要构造一个哈希图,它可以为您提供新的数据字符串来应用于每个ID。当然,这是通过读取修饰符文件来实现的。

    一旦填充了这个hasmap,您就可以浏览数据文件的每一行,读取行中间的ID,并生成上面描述的新行。

    我也不是Perl专家,但我认为程序非常简单。如果您需要帮助来写它,请要求它:-)

        3
  •  2
  •   Alexandr Ciornii    15 年前

    对于Perl,您应该使用substr来获取id_号,特别是在id_号的宽度恒定的情况下。

    my $id_number=substr($str, 500, id_number_length);
    

    之后,如果$id_号在范围内,则应使用SUBSTR替换其余文本。

    substr($str, -300,300, $new_text);
    

    Perl的正则表达式非常快,但在本例中不是。

        4
  •  1
  •   Hynek -Pichi- Vychodil Paulo Suassuna    15 年前

    我的建议是,不要使用数据库。在这类任务中,编写良好的Perl脚本在数量级上将优于数据库。相信我,我有很多实践经验。当Perl完成时,您不会将数据导入数据库。

    当你用800个字符写1500000行时,我觉得是1.2GB。如果您有非常慢的磁盘(30MB/s),您将在40秒内读取它。有更好的50->24s,100->12s等等。但是在2Ghz的CPU上,Perl哈希查找(比如db join)的速度超过了5次查找/秒,这意味着您的CPU绑定工作将在几秒钟内完成,而IO绑定工作将在几十秒钟内完成。如果真的是10GB,数字会改变,但比例是一样的。

    您还没有指定数据修改是否改变大小(如果修改可以在适当的位置进行),因此我们不会假定它并将作为过滤器工作。您还没有指定“修改文件”的格式和修改类型。假设它由制表符分隔,如下所示:

    <id><tab><position_after_id><tab><amount><tab><data>
    

    我们将从stdin读取数据并写入stdout,脚本可以是这样的:

    my $modifier_filename = 'modifier_file.txt';
    
    open my $mf, '<', $modifier_filename or die "Can't open '$modifier_filename': $!";
    my %modifications;
    while (<$mf>) {
       chomp;
       my ($id, $position, $amount, $data) = split /\t/;
       $modifications{$id} = [$position, $amount, $data];
    }
    close $mf;
    
    # make matching regexp (use quotemeta to prevent regexp meaningful characters)
    my $id_regexp = join '|', map quotemeta, keys %modifications;
    $id_regexp = qr/($id_regexp)/;     # compile regexp
    
    while (<>) {
      next unless m/$id_regexp/;
      next unless $modifications{$1};
      my ($position, $amount, $data) = @{$modifications{$1}};
      substr $_, $+[1] + $position, $amount, $data;
    }
    continue { print }
    

    在我的笔记本电脑上,150万行、1800个查找ID、1.2GB数据大约需要半分钟。对于10GB,不应超过5分钟。它对你来说合理、快捷吗?

    如果您开始认为自己不是IO绑定的(例如,如果使用某些NAS),而是CPU绑定的,那么您可以牺牲一些可读性并对此进行更改:

    my $mod;
    while (<>) {
      next unless m/$id_regexp/;
      $mod = $modifications{$1};
      next unless $mod;
      substr $_, $+[1] + $mod->[0], $mod->[1], $mod->[2];
    }
    continue { print }
    
        5
  •  0
  •   Community CDub    7 年前

    您几乎可以肯定地使用数据库,如 MikeyB suggested .

    如果出于某种原因不想使用数据库,那么如果修改列表适合内存(目前它将在1800行),那么最有效的方法是用hashtable填充修改,正如建议的那样 yves Baumes .

    如果达到即使修改列表变大的程度,您也需要根据它们的ID对这两个文件进行排序,然后执行 表合并 ——基本上:

    1. 将输入文件“top”处的ID与修改文件“top”处的ID进行比较
    2. 如果匹配,则相应地调整记录
    3. 把它写出来
    4. 从具有(字母或数字)最低ID的文件中丢弃“top”行,并从该文件中读取另一行。
    5. 转到1。

    在幕后,如果使用单个SQL执行此更改,数据库几乎肯定会使用列表合并。 UPDATE 命令。

        6
  •  0
  •   hpavc    15 年前

    处理好sqlloader或datadump决策。就这样。