我必须处理非常大的纯文本文件(超过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版本。
答案 0 :(得分:6)
我建议的方法(按照希望的顺序)将这个数据处理为:
使用数据库处理所有那些减慢文本文件处理速度的细节(查找您关心的记录,修改数据,将其存储回数据库)。在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对这两个文件进行排序,然后执行列表合并 - 基本上:
在幕后,如果使用单个SQL UPDATE
命令执行此更改,数据库几乎肯定会使用列表合并。
答案 5 :(得分:0)
关于sqlloader或datadump决策的好处。这是要走的路。