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

时间:2009-05-11 16:27:30

标签: algorithm perl awk sed large-files

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

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

数据文件(应该修改)包含1500000行,每行都是例如800字符长。每一行都是唯一的,并且只包含一个身份编号,每个身份编号都是唯一的)

修饰符文件是例如长度为1800行,包含一个标识号,以及应在数据文件中修改的数量和日期。

我刚刚将修改器文件(使用Vim正则表达式)转换为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 个答案:

答案 0 :(得分:6)

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

  1. 一个数据库(即使是一个带有索引的简单的基于SQLite的数据库在10GB文件上的性能也比sed / awk好得多)
  2. 包含固定记录长度的平面文件
  3. 包含可变记录长度的平面文件
  4. 使用数据库处理所有那些减慢文本文件处理速度的细节(查找您关心的记录,修改数据,将其存储回数据库)。在Perl的情况下查看DBD :: SQLite。

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

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

答案 1 :(得分:3)

我建议你用Perl编写一个程序(因为我不是sed / awk guru而且我不是他们完全有能力的。)

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

填充完这个hasmap后,您可以浏览数据文件的每一行,读取行中间的ID,然后生成如上所述的新行。

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

答案 2 :(得分:2)

使用perl你应该使用substr来获取id_number,特别是如果id_number具有恒定的宽度。

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

之后,如果$ id_number在范围内,则应使用substr替换剩余文本。

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

Perl的正则表达式非常快,但在这种情况下不是。

答案 3 :(得分:1)

我的建议是,不要使用数据库。写得好的perl脚本在这类任务中的性能将超过数据库。相信我,我有很多实践经验。当perl完成时,你不会将数据导入数据库。

当你用800个字符写1500000行时,对我来说似乎是1.2GB。如果您的磁盘速度很慢(30MB / s),您将在40秒内读取它。有更好的50 - > 24s,100 - > 12秒左右。但是2GHz CPU上的perl哈希查找(比如db join)速度高于5Mlookups / s。这意味着您的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 }

答案 4 :(得分:0)

您几乎肯定会使用数据库,MikeyB suggested

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

如果你达到甚至修改列表变得庞大的程度,你需要按照他们的ID对这两个文件进行排序,然后执行列表合并 - 基本上:

  1. 将输入文件“顶部”的ID与修改文件“顶部”的ID进行比较
  2. 如果匹配
  3. ,则相应地调整记录
  4. 写出来
  5. 从具有(字母或数字)最低ID的文件中删除“顶部”行,并从该文件中读取另一行
  6. 转到1。
  7. 在幕后,如果使用单个SQL UPDATE命令执行此更改,数据库几乎肯定会使用列表合并。

答案 5 :(得分:0)

关于sqlloader或datadump决策的好处。这是要走的路。