我有一个包含2亿行的10GB文件。我需要获得此文件的唯一行。
我的代码:
while(<>) {
chomp;
$tmp{$_}=1;
}
#print...
我只有2GB内存。我该如何解决这个问题?
答案 0 :(得分:5)
在大多数情况下,您可以将该行存储为哈希中的键。但是,当你得到这么大的时候,这真的不是很有效率。在这种情况下,您最好使用数据库。
要尝试的一件事是the Berkeley Database,它包含在Unix(BDB)中。现在,它显然归Oracle所有。
Perl可以使用BerkeleyDB模块与BDB数据库通信。实际上,您甚至可以将tie Perl哈希值发送到BDB数据库。完成此操作后,您可以使用普通的Perl哈希来访问和修改数据库。
BDB相当强大。比特币使用它,SpamAssassin也是如此,因此很有可能它可以处理您必须创建的数据库类型以便找到重复的行。如果您已经安装了DBD,编写一个程序来处理您的任务不应该花那么长时间。如果它不起作用,你就不会浪费太多时间。
我唯一能想到的是使用一个速度更慢,更复杂的SQL数据库。
也许我在想这个......
我决定尝试一个简单的哈希。这是我的计划:
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
use autodie;
use constant DIR => "/usr/share/dict";
use constant WORD_LIST => qw(words web2a propernames connectives);
my %word_hash;
for my $count (1..100) {
for my $file (WORD_LIST) {
open my $file_fh, "<", DIR . "/$file";
while (my $word = <$file_fh>) {
chomp $word;
$word_hash{"$file-$word-$count"} = $word;
}
}
}
读入的文件总共包含大约313,000行。我这样做了100次以获得一个包含31,300,000个密钥的哈希值。它尽可能低效。每一把钥匙都是独一无二的。内存量将是巨大的。然而...
有效。尽管程序效率低下,但大约需要10分钟才能运行,并且大约需要6千兆字节。但是,大部分都是在虚拟内存中。奇怪的是,即使它正在运行,吞噬内存,占用98%的CPU,我的系统并没有真正放慢这么多。我想这个问题真的是你期待什么类型的表现?如果运行大约10分钟对你来说不是一个问题,并且你不希望经常使用这个程序,那么可能只是为了简单并使用简单的哈希。
我现在正在从Oracle下载DBD,编译它并安装它。我将使用DBD尝试相同的程序,看看会发生什么。
完成工作后,我认为如果安装了MySQL,使用Perl DBI会更容易。我必须:
/usr/local/BerkeleyDB
,并将其安装为/usr/local/BerkeleyDB.5.3
。创建链接解决了问题。总而言之,安装BerkeleyDB大约需要1/2个小时。安装完成后,修改我的程序非常简单:
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
use autodie;
use BerkeleyDB;
use constant {
DIR => "/usr/share/dict",
BDB_FILE => "bdb_file",
};
use constant WORD_LIST => qw(words web2a propernames connectives);
unlink BDB_FILE if -f BDB_FILE;
our %word_hash;
tie %word_hash, "BerkeleyDB::Hash",
-Filename => BDB_FILE,
-Flags => DB_CREATE
or die qq(Cannot create DBD_Database file ") . BDB_FILE . qq("\n);
for my $count (1..10) {
for my $file (WORD_LIST) {
open my $file_fh, "<", DIR . "/$file";
while (my $word = <$file_fh>) {
chomp $word;
$word_hash{"$file-$word-$count"} = $word;
}
}
}
我所要做的就是添加几行。
运行该程序令人失望。它不是更快,但更快,更慢。使用纯哈希需要花费超过2分钟仅需13秒。
然而,它使用了更少的内存。虽然旧程序吞噬了千兆字节,但BDB版本几乎没有使用兆字节。相反,它创建了一个20MB的数据库文件。
但是,在虚拟机和廉价内存的这些日子里,它取得了什么成就?在虚拟内存和良好内存处理之前的过去,如果程序使用了所有内存(并且内存以兆字节而不是千兆字节为单位),则程序会使计算机崩溃。现在,如果您的程序需要的内存超过可用内存,那么它只会被赋予虚拟内存。
所以,最后,使用Berkeley数据库不是一个好的解决方案。无论我使用tie
在编程时节省了什么都浪费在安装过程中。而且,它很慢。
使用BDB只是使用DBD文件而不是内存。现代操作系统也会这样做,而且速度更快。为什么操作系统会为你处理它?</ p>
使用数据库的唯一原因是您的系统确实没有所需的资源。 2亿行是一个大文件,但现代操作系统可能会好起来的。如果您的系统确实没有资源,请在另一个系统上使用SQL数据库,而不是DBD数据库。
答案 1 :(得分:5)
正如我评论David的回答,数据库是要走的路,但是一个好的可能是DBM::Deep
,因为它的纯Perl易于安装和使用;它本质上是一个绑定到文件的Perl哈希。
use DBM::Deep;
tie my %lines, 'DBM::Deep', 'data.db';
while(<>) {
chomp;
$lines{$_}=1;
}
这基本上就是你已经拥有的,但哈希现在是一个与文件绑定的数据库(这里是data.db),而不是保存在内存中。
答案 2 :(得分:5)
如果您不关心保留订单,我打赌以下内容比以前发布的解决方案更快(例如DBM :: Deep):
sort -u file
答案 3 :(得分:4)
您可以考虑为每一行计算哈希码,并跟踪(哈希,位置)映射。你不需要复杂的哈希函数(甚至是大哈希);实际上,如果主要关心的是内存使用,“较小”优于“更独特”。甚至CRC,或总结字符代码,也可能。关键在于不保证在这个阶段的独特性 - 只是将候选匹配从2亿缩小到几十。
对于每一行,计算哈希并查看您是否已有映射。如果这样做,那么对于映射到该哈希的每个位置,读取该位置的线并查看线是否匹配。如果其中任何一个做了,请跳过该行。如果没有,或者您没有该哈希的任何映射,请记住(哈希,位置),然后打印该行。
注意,我说的是“位置”,而不是“行号”。为了在不到一年的时间内完成这项工作,您几乎肯定必须能够寻找合适的生产线,而不是找到通往#1392499的路线。
答案 4 :(得分:3)
如果您不关心时间/ IO约束,也不关心磁盘约束(例如,您还有10个GB空间),则可以执行以下哑算法:
1)读取文件(听起来有50个字符行)。在扫描时,请记住最长的线长$L
。
2)分析前3个字符(如果你知道char#1是相同的 - 说"["
- 分析位置N中可能有更多不同的3个字符。)
3)对于包含3个字符$ XYZ的每一行,将该行追加到文件3char。$ XYZ并保留该文件中哈希值的行数。
4)当你的整个文件以这种方式拆分时,你应该有一大堆(如果文件只有AZ,然后是26 ^ 3)的小文件,最多4个文件,每个大于2GB。
5)将原始文件移动到“已处理”目录。
6)对于每个大文件(> 2GB),选择接下来的3个字符位置,并重复步骤#1-#5,新文件为6char。$ XYZABC
7)泡沫,冲洗,重复。最终你将得到两个选项中的一个:
8a)一堆较小的文件,每个文件都在2GB以下,所有文件都有相互不同的字符串,每个文件(由于它的大小)都可以通过标准的“藏匿到哈希”解决方案单独处理。
8b)或者,大多数文件都较小,但是,您在重复步骤7时为所有$L
个字符排除了&gt; 2GB文件,并且您仍然有1-4个大文件。猜猜 - 从那以后
那些最多4个大文件在位置1 .. $ L的文件中具有相同的字符,它们也可以在你的问题中使用“藏匿哈希”方法进行处理,因为它们不会包含多于一个尽管它们的大小,但很少有明显的线条!
请注意,这可能需要 - 在最差的发行版 - 10GB * L / 3
磁盘空间,但如果您将步骤#5从“移动”更改为“删除”,则只需要20GB的磁盘空间。
瞧。完成。
作为替代方法,请考虑对您的线进行哈希处理。我不是哈希专家,但你应该能够将一行压缩成一个哈希<5倍线大小的恕我直言。
如果您想了解这一点,您将在第一遍中对字符序列进行频率分析,然后以这种方式进行压缩/编码。
答案 5 :(得分:1)
如果你有更多的处理器,并且至少有15GB的可用空间,并且你的存储空间足够快,你可以尝试一下。这将以paralel处理它。
split --lines=100000 -d 4 -d input.file
find . -name "x*" -print|xargs -n 1 -P10 -I SPLITTED_FILE sort -u SPLITTED_FILE>unique.SPLITTED_FILE
cat unique.*>output.file
rm unique.* x*
答案 6 :(得分:0)
您可以将文件分成10个1 GB文件然后一次读取一个文件,从该文件中排序行并在排序后将其写回。打开所有10个文件并将它们合并回一个文件(确保以正确的顺序合并它们)。打开输出文件以保存唯一的行。然后一次读取一行合并文件,保留最后一行进行比较。如果最后一行和当前行不匹配,请写出最后一行并将当前行保存为最后一行进行比较。否则从合并文件中获取下一行。这将为您提供一个包含所有唯一行的文件。
执行此操作可能需要一段时间,但如果您的内存有限,那么打破文件并处理其中的部分内容将会起作用。
在写出文件时可以进行比较,但这会有点复杂。
答案 7 :(得分:0)
为什么要使用perl呢? posix shell:
sort | uniq
完成了,我们去喝啤酒。