我确实必须处理非常大的纯文本文件(超过 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 的过时版本。

有帮助吗?

解决方案

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

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

使用数据库可以处理所有那些减慢文本文件处理速度的小细节(查找您关心的记录、修改数据、将其存储回数据库)。看看 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 }

您几乎肯定应该使用数据库,因为 米奇B建议.

如果您出于某种原因不想使用数据库,那么如果修改列表适合内存(目前为 1800 行),最有效的方法是使用哈希表填充修改,如建议的那样 伊夫·波姆斯.

如果修改列表变得很大,则需要按 ID 对两个文件进行排序,然后执行 列表合并 - 基本上:

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

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

在SQLLOADER或datadump决定好买卖。这是要走的路。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top