【问题标题】:Opening Files for Reading too Large for Memory打开文件以读取太大内存
【发布时间】:2014-07-09 17:48:12
【问题描述】:

我有一个简单的 Perl 脚本,它读取预期格式的文件,将一些相关信息保存到散列中,对散列进行一些操作,然后关闭文件并移动到下一个文件。

我需要考虑我的系统内存小于文件参数大小的情况,所以我想,为什么不试试呢?所以我将我的 VirtualBox 限制为 1GB 的 RAM,创建了一个 1.5GB 的列表,并将其作为输入提供给它。没有错误被抛出,它只是运行和运行。

我使用“free”命令查看了正在使用的系统内存,它显示近 99% 的内存正在使用,并且随着时间的推移完全没有增加。我的 Perl 脚本中有一些中断处理代码,当我按下它时,我的 vbox 变得无响应,我必须重新启动它。

我需要做的是输入几个格式如下的邮件列表

1-bob@bob.com

并向邮件列表中的每个电子邮件地址发送电子邮件。我通过将所有电子邮件地址添加到哈希中来跟踪它们。在处理每个地址时(即添加到哈希中),我还会对其进行验证并发送电子邮件给它(如果它是好的)。

有没有更好的方法来测试这个?我做错了吗?

use strict;
use warnings;

my %email_db;

while (<>) {
  chomp;
  my ($id, $email) = split /-/;
  push @{ $email_db{$id} }, $email;
}

输入文件的预期数量可以从 1 到 5 不等(我在这里假设 - 问题并不具体),就文件大小给出的示例是 100MB 系统内存和 10GB 文件大小。末尾散列中的条目数未知。

散列的每个元素都有

key   = unique ID
value = array of email addresses for that ID

【问题讨论】:

  • 如果可以的话,将输入文件分成更小的部分,对适合系统内存的那些部分进行分析。将结果写入(序列化)到磁盘。最后整理这些结果。无论您的编程环境如何,请考虑类似 MapReduce 的过程:en.wikipedia.org/wiki/MapReduce
  • 这不会解决您的问题,但您应该遍历@ARGV内容,除非您出于其他原因需要索引,所以for my $file (@ARGV) { .. }。使用 split /-/ 而不是 split("\055")(八进制 55 是 ASCII 连字符的代码点)。并使用 open my $list_fh, '&lt;', $file 之类的词法文件句柄
  • 答案在于曾经被评论的#..do more stuff w/the info。就目前而言,您正在将文件中的所有数据读入哈希%email_db。除非您的算法允许增量处理数据,否则无法按照您的要求进行处理,但除非您解释需要做什么,否则我们无法为您提供帮助
  • @Borodin 我在问题中添加了“目标”部分,以向您展示需要做什么。该算法一点也不特别。我只是逐行解析文件,每一行都有一封电子邮件,我得到了电子邮件地址,我用电子邮件地址做事(参考编辑)。
  • @ScottJamesWalter:我已经修复了您的代码,假设您只需要阅读命令行上的所有文件,在这种情况下您不需要显式打开它们。如果已处理的电子邮件地址列表不适合内存,您必须以某种方式将其存储在磁盘上:那些说您“不允许这样做”的人必须允许一种可行的方法

标签: perl memory-management


【解决方案1】:

这是一个使用 SQLite 数据库而不是 Perl 哈希来存储电子邮件信息的解决方案。

我无法添加任何代码来发送电子邮件或报告组数,因为您没有指定这些部分。

作为替代方案,您可能希望等到所有输入文件都被读取并插入到数据库中,然后再发送任何电子邮件。

如果您从emails 表中删除TEMPORARY 属性,以便在程序完成时它不会消失,那么调试此代码可能会更容易。

use strict;
use warnings;

use DBI;

my $dbh = DBI->connect(
  'dbi:SQLite:dbname=email_db.sqlite', '', '',
  { RaiseError => 1, PrintError => 0 }
);

$dbh->do(<<END_SQL);
CREATE TEMPORARY TABLE emails (
  group_id INTEGER,
  email    TEXT,
  UNIQUE (group_id, email)
)
END_SQL

my $insert = $dbh->prepare(<<END_SQL);
INSERT INTO emails (group_id, email)
VALUES (?, ?)
END_SQL

$insert->{RaiseError} = 0;

while (<>) {
  next unless /-/;
  chomp;
  my ($group_id, $email) = split /-/;
  if ($insert->execute($group_id, $email)) {
    # Send email
  }
  else {
    my $errstr = $insert->errstr;
    die $errstr unless $errstr =~ /UNIQUE constraint failed/;
  }
}

my $sort = $dbh->prepare(<<END_SQL);
SELECT group_id, count(email)
FROM emails
GROUP BY group_id
ORDER BY count(email) DESC
END_SQL

$sort->execute;

while (my $row = $sort->fetchrow_arrayref) {
  printf "Group %d (%d emails)\n", @$row;
}

【讨论】:

  • 这是一个很好的解决方案,我根据自己的目的对其进行了调整。我唯一不知道该怎么做,是以这种方式从数据库返回一个列表到 STDOUT:第一个 -> 最后一个,带有大多数电子邮件地址的 ID -> 带有最少电子邮件地址的 ID。我猜你会排序你的数据库,然后在打印时一次抓取一行。
  • 另一件事似乎很时髦:如果我有一个文件包含:“1-test@test.com 2-test@test.com”,它应该添加这两个。它永远不应该添加具有完全相同密钥和 ID 的电子邮件。
  • @ScottJamesWalter:好的,我已经进行了这些更改。看看
【解决方案2】:

为什么不使用 SDBM 哈希?它是链接到文件的哈希。这是一个“精简”解决方案,不需要安装完整的数据库。只是普通的 Perl。密钥大小 + 数据必须小于 1008 字节。

use Fcntl; # Needed by SDBM_File
use SDBM_File;

# 'myemails' is the name of the file, and 2 files will be created: 
# myemails.pag and myemails.dir
tie(%h, 'myemails', 'filename', O_RDWR|O_CREAT, 0666) 
or die "Couldn't tie SDBM file 'filename': $!; aborting";
# Now read and change the hash
$h{'bob@somewhere.com'} = 1; # This email now exists in hash.
print $h{'bob@somewhere.com'}."\n";
...
untie %h;

我使用了一个 500,000 行的文本文件,但脚本在我们的 linux 机器上运行。

另外,这个文件将在您运行脚本的时候出现,因此您不会每天/每周/每月发送到欺骗性电子邮件地址。

要清除电子邮件,请将 myemails.dir 和 myemails.pag 移至备份文件,例如:myemails2014-07.dir 和 myemails-2014-07.pag。

编辑:有人知道 SDBM 中的键数限制或 SDBM 文件的最大大小吗?这适用于 100 万个电子邮件地址吗?我有一个包含 10,000 个密钥的 SDBM 文件。

【讨论】:

  • 如果您在 SDBM 中有几百个电子邮件条目会怎样。您可以订购整个哈希并将它们打印到标准输出而不消耗所有内存吗?此外,我的哈希有点奇怪。它有一个 ID 作为键,一个数组作为值。这种结构能用吗?
  • @ScottJamesWalter:是的,您可以将它们打印到标准输出,但您为什么要这样做?如果您有数百或数千封电子邮件,正如您的 1.5GB 输入文件大小所建议的那样,您不想将电子邮件输出到文本文件吗?然后从那里操作文本文件?您可以通过以下方式获取哈希的所有键:@a=sort(keys %h));现在只需将它们打印出来。
  • 这只是规范的一部分。感谢您的建议!
【解决方案3】:

我想推荐模块Tie::File::AsHashTie::File

我自己都没有使用过,所以没有任何经验可以分享。

【讨论】:

    猜你喜欢
    • 2020-01-26
    • 1970-01-01
    • 2019-06-29
    • 2014-05-05
    • 2015-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多