C# 内存受限的字符串外部排序,重复项合并&;已计算,在关键服务器上(数十亿个文件名)

C# 内存受限的字符串外部排序,重复项合并&;已计算,在关键服务器上(数十亿个文件名),c#,algorithm,sorting,dictionary,large-data,C#,Algorithm,Sorting,Dictionary,Large Data,我们的服务器在其日志文件夹中生成类似于{c521c143-2a23-42ef-89d1-557915e2323a}-sign.xml的文件。第一部分是GUID;第二部分是名称模板 我想计算具有相同名称模板的文件数。例如,我们有 {c521c143-2a23-42ef-89d1-557915e2323a}-sign.xml {aa3718d1-98e2-4559-bab0-1c69f04eb7ec}-hero.xml {0c7a50dc-972e-4062-a60c-062a51c7b32c}-s

我们的服务器在其日志文件夹中生成类似于
{c521c143-2a23-42ef-89d1-557915e2323a}-sign.xml
的文件。第一部分是GUID;第二部分是名称模板

我想计算具有相同名称模板的文件数。例如,我们有

{c521c143-2a23-42ef-89d1-557915e2323a}-sign.xml
{aa3718d1-98e2-4559-bab0-1c69f04eb7ec}-hero.xml
{0c7a50dc-972e-4062-a60c-062a51c7b32c}-sign.xml
结果应该是

sign.xml,2
hero.xml,1
可能的名称模板的总类型未知,可能超过
int.MaxValue

服务器上的文件总数未知,可能超过
int.MaxValue

要求

最终结果应按名称模板排序

运行该工具的服务器是超关键的。在运行工具之前,我们应该能够告诉内存使用率(MB)和生成的临时文件的数量(如果有),而不知道日志文件夹的任何特征

我们使用C语言

我的想法

  • 对于前5000个文件,计算出现次数,将结果写入
    Group1.txt
  • 对于第二个5000个文件,计算出现次数,将结果写入
    Group2.txt
  • 重复此操作,直到处理完所有文件。现在我们有一堆组文件
然后我合并所有这些组文件

   Group1.txt     Group2.txt   Group3.txt     Group4.txt   
       \            /            \                /
       Group1-2.txt                Group3-4.txt
                  \                 /
                    Group1-4.txt
Group1-4.txt
是最终结果

我和我朋友之间的分歧在于我们如何计算发生的事情

我建议用字典。文件名模板是关键。设m为分区大小。(在这个例子中是5000。)然后时间复杂度O(m),空间复杂度O(m)

我的朋友建议对名称模板进行排序,然后在一次传递中计算出现的次数,因为现在相同的名称模板都在一起了。时间复杂度O(m logm),空间复杂度O(m)

我们无法说服对方。你们看到这两种方法有什么问题吗?

这是一个非常好的问题

考虑到你打算分批处理5000次的结果,我不认为记忆优化会特别重要,所以我们可能会像亚当·桑德勒(Adam Sandler)的糟糕电影一样忽略这一方面,转而关注更令人兴奋的东西。此外,仅仅因为一些计算使用了更多的RAM并不一定意味着它是一个糟糕的算法。从来没有人抱怨过查表

然而,我确实同意字典方法在计算上更好,因为它更快。关于备选方案,为什么要执行不必要的排序,即使它很快?后者的“O(m log m)”最终比“O(m)”慢

真正的问题是什么? 由于RAM不在方程中,问题本质上是计算问题。算法中的任何“性能问题”对于首先遍历文件系统所需的时间来说都是无关紧要的

这才是真正的挑战所在。下次可能会有问题吗

编辑:displayName很好地说明了如何使用Hadoop-非常适合并发作业和计算


祝你好运

您的问题非常适合Map Reduce。好消息:您不需要从C#迁移到Java(Hadoop)作为

通过LINQ,您已经具备了在C#中执行Map Reduce的基本执行元素。这可能是相对于外部排序的一个优势,尽管外部排序背后的观察毫无疑问。有“你好,世界!”Map Reduce已经在C#中使用LINQ实现,应该可以让您开始使用


如果您确实转向Java,关于它的最全面的教程之一就是。谷歌关于Hadoop和MapReduce,你将获得大量信息和众多优秀的在线视频教程

此外,如果您希望转向Java,您的以下要求:

  • 排序结果
  • 关键RAM使用
肯定会得到满足,因为它们是您从Hadoop中的Map Reduce作业中获得的内置实现。

如何在您的方法中“合并组文件”?在最坏的情况下,每一行都有一个不同的名称模板,因此每个组文件中有5000行,并且每次合并都会使行数加倍,直到内存溢出


您的朋友离答案更近了,这些中间文件需要排序,这样您就可以逐行读取它们并合并它们以创建新文件,而无需将它们全部保存在内存中。这是一个众所周知的问题,这是一个令人担忧的问题。排序后,您可以对结果进行计数。

IDK如果已研究外部排序与重复项的计数合并。我确实找到了一篇1983年的论文(见下文)。通常,排序算法的设计和研究都假设对象按键排序,因此重复键具有不同的对象。可能有一些关于这方面的现有文献,但这是一个非常有趣的问题。可能它只是被认为是一个紧凑字典与外部合并排序相结合的应用程序

在很少的内存中存储大量字符串的高效词典是一个研究得很好的问题。大多数有用的数据结构可以包括每个字的辅助数据(在我们的例子中,是dup计数)


TL:DR总结了一些有用的想法,因为我在这个答案的主体部分对很多事情进行了太多的详细阐述:

  • 当字典大小达到阈值时,而不是在固定数量的输入文件之后,批处理边界。如果在一组5000个字符串中有很多重复项,您仍然不会使用太多内存。通过这种方式,您可以在第一次通过时找到更多的副本

  • 排序的批处理使合并速度更快。您可以而且应该合并多->一,而不是二进制合并。使用PriorityQueue确定哪个输入文件具有下一行

  • 在排序时避免内存的突然占用