`uniq`没有排序一个巨大的文本文件?

Gra*_*ham 5 bash awk

我有一个愚蠢的大文本文件(即今天的40千兆字节),我想在没有排序文件的情况下过滤唯一的行.

该文件具有unix行结尾,并且所有内容都匹配[[:print:]].我尝试了以下awk脚本只显示唯一的行:

awk 'a[$0] {next} 1' stupid.txt > less_stupid.txt
Run Code Online (Sandbox Code Playgroud)

我的想法是,我通过引用其元素来填充数组,使用文件的内容作为键,然后跳过已经在数组中的行.但这有两个原因失败 - 首先是因为它莫名其妙地不起作用(即使是在小型测试文件上),其次是因为我知道在将整组唯一行加载到内存之前我的系统会耗尽内存通过awk.

搜索后,我发现这个答案建议:

awk '!x[$0]++'
Run Code Online (Sandbox Code Playgroud)

虽然这适用于小文件,但在读取整个文件之前也会耗尽内存.

什么是更好(即工作)的解决方案?我对任何事情都持开放态度,尽管我更倾向于使用我所知道的语言解决方案(bash&awk,因此标签).在尝试可视化问题时,我提出的最好的方法是存储一系列行校验和或MD5而不是行本身,但这只会节省一点空间并冒着校验和冲突的风险.

任何提示都会非常受欢迎.告诉我这是不可能的也是受欢迎的,所以我不想试图解决它.:-P

hen*_*ber 6

awk '!x[$0]++'技巧是最优雅的解决方案,删除重复的文件或流不排序之一.但是,它在内存方面效率低,不适合大文件,因为它将所有独特的行保存到内存中.

但是,更有效的实现方法是保存数组中行的常量长度哈希表示而不是整行.你可以Perl在一行中实现这一点,它与awk脚本非常相似.

perl -ne 'use Digest::MD5 qw(md5_base64); print unless $seen{md5_base64($_)}++' huge.txt
Run Code Online (Sandbox Code Playgroud)

这里我使用md5_base64而不是md5_hex因为base64编码需要22个字节,而十六进制表示32.

但是,由于hashes每个键的Perl实现仍然需要大约120字节,因此您的内存可能很快就会占用大量文件.

在这种情况下,解决方案是以块的形式处理文件,手动拆分或使用带有--pipe, - keep-order和--block选项的GNU Parallel(利用重复行不远的事实,如你提到过).以下是您可以使用的方法parallel:

cat huge.txt | pv | 
parallel --pipe --keep-order --block 100M -j4 -q \
perl -ne 'use Digest::MD5 qw(md5_base64); print unless $seen{md5_base64($_)}++' > uniq.txt
Run Code Online (Sandbox Code Playgroud)

--block 100M选项告诉parallel并行处理100MB的块输入.-j4意味着并行启动4个过程.这里一个重要的论点是,--keep-order因为您希望唯一的行输出保持相同的顺序.我已经包含pv在管道中,以便在长时间运行的进程执行时获得一些不错的统计信息.

在使用随机数据1GB文件执行的基准测试中,我使用上述设置达到了130MB /秒的吞吐量,这意味着您可以在4分钟内删除40GB文件(如果您有足够快的硬盘能够写入这个率).

其他选择包括:

以下是使用Perl 的Bloom :: Faster模块的示例:

perl -e 'use Bloom::Faster; my $f = new Bloom::Faster({n => 100000000, e => 0.00001}); while(<>) { print unless $f->add($_); }' huge.txt > uniq.txt
Run Code Online (Sandbox Code Playgroud)

您可以安装Bloom::Fastercran(sudo cran然后install "Bloom::Faster")

说明:

  • 您必须指定概率错误率e和可用桶数n.每个桶所需的内存大约为2.5个字节.如果您的文件有1亿个独特的行,那么您将需要1亿个桶和大约260MB的内存.
  • $f->add($_)函数将一行的哈希值添加到过滤器,并true在键(即此处的行)重复时返回.
  • 您可以估算文件中唯一行的数量,使用dd if=huge.txt bs=400M count=1 | awk '!a[$0]++' | wc -l(400MB)解析文件的一小部分并将该数字乘以100(40GB).然后将n选项设置得更高一些,以确保安全.

在我的基准测试中,这种方法实现了6MB/s的处理速度.您可以将此方法与parallel上面的GNU 建议结合使用,以利用多个内核并实现更高的吞吐量.


Nou*_*him 1

如果存在大量重复,一种可能性是将文件分割split(1)成可管理的部分,并使用像 sort/uniq 这样的传统方法来总结独特的行。这将比实际作品本身短。之后,您可以比较各个部分以得出实际的摘要。