sed优化(基于较小数据集的大文件修改)
-
21-08-2019 - |
题
我确实必须处理非常大的纯文本文件(超过 10 GB,是的,我知道这取决于我们应该称之为大的文件),并且行很长。
我最近的任务涉及根据另一个文件中的数据进行一些行编辑。
数据文件(应该修改)包含 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
它非常慢,因为它必须读取每行的每个模式。
难道就没有更好的办法吗?
笔记: 我不是程序员,从未(在学校)学过算法。我可以在服务器上使用 awk、sed、perl 的过时版本。
解决方案
我建议的方法(按期望的顺序)是将这些数据处理为:
- 数据库(即使是带有索引的简单的基于 SQLite 的数据库,在 10GB 文件上的性能也会比 sed/awk 好得多)
- 包含固定记录长度的平面文件
- 包含可变记录长度的平面文件
使用数据库可以处理所有那些减慢文本文件处理速度的小细节(查找您关心的记录、修改数据、将其存储回数据库)。看看 Perl 中的 DBD::SQLite。
如果您想坚持使用平面文件,则需要在大文件旁边手动维护索引,以便您可以更轻松地查找需要操作的记录号。或者,更好的是,也许是您的身份证号码 是 你的记录数字?
如果您有可变记录长度,我建议转换为固定记录长度(因为看起来只有您的 ID 是可变长度)。如果您做不到这一点,也许任何现有数据都不会在文件中移动?然后,您可以维护前面提到的索引并根据需要添加新条目,不同之处在于,您现在指向文件中的绝对位置,而不是指向记录号的索引。
其他提示
我建议你用Perl编写的(因为我不是一个sed / awk的大师,我不他们究竟能)一PROGRAMM。
您“算法”很简单:你需要构建,首先,一个HashMap的它可以给你新的数据串适用于每个ID。这得以实现阅读课程的改性剂文件。
在此hasmap在人口可能会浏览数据文件的每一行,读取在该行的中间的ID,并且如你已经如上所述产生的新的行。
我不是一个Perl大师,但我却认为,PROGRAMM是相当简单的。如果您需要帮助写出来,问它: - )
使用的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的正则表达式是非常快的,但不是在这种情况下。
我的建议是,不要使用数据库。在此类任务中,编写良好的 Perl 脚本将在数量级上优于数据库。相信我,我有很多实践经验。当 perl 完成时,您不会将数据导入数据库。
当你写 1500000 行 800 个字符时,对我来说似乎是 1.2GB。如果您的磁盘速度非常慢(30MB/s),您将在 40 秒内读取它。更好的是 50 -> 24 秒,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 }
在SQLLOADER或datadump决定好买卖。这是要走的路。