删除重复项的标准方法是对文件进行排序,然后执行顺序传递以删除重复项。对 5 亿行进行排序并非易事,但它肯定是可行的。几年前,我有一个日常流程是在 16 GB 的机器上对 50 到 100 GB 的数据进行排序。
顺便说一句,您也许可以使用现成的程序来做到这一点。当然,GNU 排序实用程序可以对大于内存的文件进行排序。我从来没有在 500 GB 的文件上尝试过,但你可以试一试。您可以将其与GNU Core Utilities 的其余部分一起下载。该实用程序有一个--unique 选项,因此您应该可以只使用sort --unique input-file > output-file。它使用一种类似于我在下面描述的技术。我建议先在 100 兆字节的文件上尝试,然后慢慢处理更大的文件。
使用 GNU 排序和我在下面描述的技术,如果输入目录和临时目录位于不同的物理磁盘上,它的性能会好很多。将输出放在第三个物理磁盘上,或与输入放在同一个物理磁盘上。您希望尽可能减少 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 路合并。这是通过创建优先级队列来完成的。您打开每个文件,从每个文件中读取第一行并将其连同对它来自的文件的引用一起放入队列中。然后,您从队列中取出最小的项目并将其写入输出文件。要删除重复项,请跟踪输出的上一行,如果新行与上一行相同,则不输出新行。
一旦您输出了该行,您就可以从文件中读取您刚刚输出的行的下一行,并将该行添加到优先级队列中。继续这种方式,直到清空所有文件。
不久前我发表了一系列关于sorting a very large text file 的文章。它使用我上面描述的技术。它唯一不做的是删除重复项,但这是对输出临时文件的方法和最终输出方法的简单修改。即使没有优化,该程序的性能也相当不错。它不会设置任何速度记录,但它应该能够在不到 12 小时内对 5 亿行中的重复项进行排序和删除。考虑到第二遍只处理总数据的一小部分(因为您从临时文件中删除了重复项),可能要少得多。
您可以做的一件事来加快程序的速度,即在较小的块上进行操作,并在将下一个块加载到内存中时在后台线程中对一个块进行排序。你最终不得不处理更多的临时文件,但这真的不是问题。堆操作稍微慢一些,但是通过将输入和输出与排序重叠来重新捕获额外的时间。您最终基本上免费获得了 I/O。在典型的硬盘驱动器速度下,加载 500 GB 大约需要两个半到三个小时。
看看文章系列。有许多不同的(大多是小型的)文章带您了解我描述的整个过程,并提供工作代码。我很乐意回答您可能对此提出的任何问题。