在一个大型文本文件中删除所有重复项

4
我在这个问题上遇到了困难,因此停工了一段时间。我处理非常大的数据块。每周会得到约200GB的.txt数据。该数据可以达到500 million行,其中大部分是重复的。我猜测只有20GB是唯一的。我已经制作了几个定制程序,包括哈希移除重复项、外部移除重复项,但都无法解决问题。最新的一个使用了临时数据库,但需要数天时间才能完成数据移除。
所有程序的问题是,在某个点之后它们会崩溃,而且花费了很多钱购买这些程序后,我想上网看看是否有人能够帮助。我知道这个问题以前已经有人回答过,我已经花了3个小时阅读了50个帖子,但似乎没有人和我有同样的问题,即巨大的数据集。
有人能为我推荐什么吗?需要超级精确和快速。最好不要基于内存,因为我只有32GB的RAM可供使用。

如果您没有正确标记,这个问题可能不会得到太多关注。您应该使用您打算使用的语言进行标记。 - Felix Kling
谢谢Felix,我已经完成了。 - user3194329
你能具体说明一下重复项删除的范围吗?你需要从最近一天的数据中删除重复行吗?还是最近一周?或者是从一开始就要删除?此外,除了删除重复项之外,你是否需要保持数据的“顺序”不变?如果需要,那么哪个重复项保留下来是否很重要呢? - rici
你实际上使用的是哪种编程语言?我相信在这些语言中,这个问题的解决方式都不一样。 - David G
1
200GB和500M行=每行大约400个字符...需要比较整行吗?您能提供一个示例数据吗?您使用的操作系统是什么? - clt60
大家好,这是一些数据示例。它们都是URL链接:https://www.dropbox.com/s/ae84dm1f93f73ft/test.rar 。服务器为Windows Server 2008 Xeon Quad Core,32GB内存。对于编程语言不是太挑剔,因为我可以在了解最有效的方法后进行更多的研究或请求别人创建。 - user3194329
2个回答

5
标准的去重方法是对文件进行排序,然后顺序遍历以删除重复项。排序 5 亿行并不容易,但肯定可行。几年前,我每天都会在一台 16GB 的机器上对 50 到 100 GB 的数据进行排序处理。
顺便说一下,您可能可以使用现成的程序来完成此操作。当然,GNU sort 工具可以对大于内存的文件进行排序。我从未尝试过对 500 GB 的文件进行排序,但您可以尝试一下。您可以与 GNU Core Utilities 一起下载该实用程序。该实用程序具有“--unique”选项,因此您应该能够只需使用“sort --unique input-file > output-file”即可完成操作。它使用了类似于我下面描述的技术。建议您先在 100 MB 的文件上尝试,然后逐步增加到更大的文件。
使用 GNU sort 和我下面描述的技术,如果输入和临时目录位于不同的物理磁盘上,则性能将大大提高。将输出放置在第三个物理磁盘上或与输入放置在同一物理磁盘上。您要尽可能减少 I/O 冲突。

也许有一些商业(即付费)程序可以进行排序。开发一个能高效排序大型文本文件的程序是一项不容易的任务。如果你可以花几百美元买到某些东西,而你的时间也很宝贵,那么你可能赚了。

如果您不能使用现成的程序,那么...

如果您的文本内容分布在多个较小的文件中,则问题更容易解决。您可以先对每个文件进行排序,从这些文件中删除重复项,并编写已删除重复项的排序临时文件。然后运行简单的n路归并以将文件合并为一个输出文件,其中已删除重复项。

如果您只有一个文件,则首先读入尽可能多的行到内存中,对其进行排序,删除重复项并编写临时文件。您可以继续处理整个大文件。完成后,您会得到一些已排序的临时文件,然后可以将它们合并。

伪代码如下:

fileNumber = 0
while not end-of-input
    load as many lines as you can into a list
    sort the list
    filename = "file"+fileNumber
    write sorted list to filename, optionally removing duplicates
    fileNumber = fileNumber + 1

你不必从临时文件中删除重复项,但如果你的唯一数据只占总数据的10%,那么不将重复项输出到临时文件中可以节省大量时间。

一旦所有临时文件都写好了,你需要将它们合并。根据你的描述,我估计你从文件中读取的每个块将包含大约2000万行左右。因此,你可能有25个临时文件需要处理。

现在你需要进行k路合并。这是通过创建一个优先队列来完成的。你打开每个文件,从每个文件中读取第一行,并将其与来自该行的文件的引用一起放入队列中。然后,你取出队列中最小的项目,并将其写入输出文件。为了删除重复项,你要跟踪你输出的前一行,并且如果新行与上一行相同,则不输出新行。

一旦你输出了这一行,你就从刚才输出的行所在的文件中读取下一行,并将该行添加到优先队列中。你继续这样做,直到你清空了所有的文件。

我之前发表了一系列关于如何对一个非常大的文本文件进行排序的文章。它使用了我上面描述的技术。唯一不做的事情就是去重,但这只需要对输出临时文件和最终输出方法进行简单修改即可。即使没有优化,该程序的性能也相当不错。它不会创造任何速度记录,但应该能够在不到12小时内对5亿行进行排序和去重。考虑到第二次遍历仅处理总数据的一小部分(因为已从临时文件中删除了重复项),所以可能要快得多。
为了加快程序速度,您可以操作更小的块,并在您加载下一个块到内存时在后台线程中对一个块进行排序。您最终需要处理更多的临时文件,但这真的不是问题。堆操作略慢,但通过将输入和输出与排序重叠,可以获得额外的时间。您最终可以免费获得I/O。在典型的硬盘速度下,加载500GB的数据需要大约两个半到三个小时左右。

请查看文章系列。这是许多不同的,主要是小的文章,带您完成我描述的整个过程,并呈现工作代码。如果您有任何问题,请随时问我。


我认为我会非常喜欢解决这样的问题。感谢您分享您的详细信息。我们在本周末还有另一个线程询问k路合并的问题:Perl Merge File - Miller
嗨Jim,感谢您详细的回复。我现在要去研究一下这个问题。我已经在网上搜索了一段时间,但是找不到任何商业程序可以处理我正在处理的大量数据并保持速度和准确性。 - user3194329

1
我不是算法专家,但如果数据是纯文本(或数字,无所谓),您可以尝试读取大文件并将其按前两个或三个字符写入几个文件:以“aaa”开头的所有行都去aaa.txt,以“aab”开头的所有行都去aab.txt,依此类推。您将得到许多文件,其中数据处于等价关系:单词的重复出现与该单词本身在同一文件中。现在,只需在内存中解析每个文件即可完成。 再次强调,不确定是否可行,但我会首先尝试这种方法...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接