【问题标题】:Sort a file with huge volume of data given memory constraint在给定内存限制的情况下对具有大量数据的文件进行排序
【发布时间】:2010-01-18 16:22:23
【问题描述】:

积分:

  • 我们在一天内同时处理数千个平面文件。
  • 内存限制是一个主要问题。
  • 我们为每个文件进程使用线程。
  • 我们不按列排序。文件中的每一行(记录)都被视为一列。

做不到:

  • 我们不能使用 unix/linux 的排序命令。
  • 我们无法使用任何数据库系统,无论它们多么轻巧。

现在,我们不能只加载集合中的所有内容并使用排序机制。它会耗尽所有内存,程序会出现堆错误。

在这种情况下,您将如何对文件中的记录/行进行排序?

【问题讨论】:

  • 您不能使用数据库系统有什么原因吗? DB 专为此类场景而设计,因为它们在对大量数据进行排序方面非常高效。
  • @Erika:引入轻量级未安装数据库与引入自定义编写的程序有何不同?从技术上讲,两者都在“改变系统”。
  • 试着把它提供给一个不懂编程的高管。如果你能卖给他,你就是我的导师!
  • 把这个话题的链接发邮件给他:D
  • 如果是嵌入式数据库,执行官不必知道

标签: java file sorting


【解决方案1】:

看起来你正在寻找的是 external sorting.

基本上,您首先对小块数据进行排序,将其写回磁盘,然后遍历这些数据以对所有数据进行排序。

【讨论】:

  • 根据我的研究,我的理解是,如果您在一个文件中有 1000 条记录,并且一次读取 100 条,则对这 100 条进行排序并将排序后的版本放入一个临时文件中,该文件将创建 10 个临时文件排序的文件。然后依次读取两个文件并创建另一个已排序(现在更大)的文件并删除刚刚读取的另外两个文件。继续,直到你有一个文件。严重地?现在,假设您在一个文件中有 1000 万条记录,并且您一次读取 5000 条记录,您创建了多少临时文件以及获得最终版本需要多少时间?
  • 与内存中的排序相比,外部排序总是更慢,但您不再受内存的限制。如果速度对您很重要并且您手头有几台机器,请查看 hadoop(在其他回复中提到)。它进行外部排序,所有单独的排序操作都可以在多台机器上并行发生。
  • Erika:当您合并(排序的、较小的)文件时,您可以打开两个以上的文件,仅使用两个临时文件来描述算法会稍微简单一些。但是,如果您需要一个大于排序的可用内存的文件,那么无论如何您都必须(最终)这样做,并且合并操作(相对)快,因为它需要做的就是保持 N 个文件指针打开并找到 N 个“下一条记录”中的最低值,以知道接下来要发出什么。我想调整的关键部分是选择在每个临时文件中保留多少条记录。
【解决方案2】:

您可以读取较小部分的文件,对它们进行排序并将它们写入临时文件。然后你再次按顺序读取其中两个并将它们合并到一个更大的临时文件中,依此类推。如果只剩下一个,则您的文件已排序。基本上这就是对外部文件执行的 Megresort 算法。它可以很好地扩展任意大文件,但会导致一些额外的文件 I/O。

编辑:如果您对文件中行的可能差异有所了解,则可以采用更有效的算法(分布排序)。简化后,您将读取原始文件一次并将每一行写入一个临时文件,该文件仅包含具有相同第一个字符(或一定范围的第一个字符)的行。然后按升序遍历所有(现在很小的)临时文件,在内存中对它们进行排序并将它们直接附加到输出文件中。如果临时文件太大而无法在内存中排序,您可以根据行中的第二个字符重复相同的过程,依此类推。因此,如果您的第一个分区足以生成足够小的文件,那么无论文件有多大,您将只有 100% 的 I/O 开销,但在最坏的情况下,它可能会变得比性能明智的稳定合并排序要多得多。

【讨论】:

  • 根据我的研究,我的理解是,如果您在一个文件中有 1000 条记录,并且一次读取 100 条,则对这 100 条进行排序并将排序后的版本放入一个临时文件中,该文件将创建 10 个临时文件排序的文件。然后依次读取两个文件并创建另一个已排序(现在更大)的文件并删除刚刚读取的另外两个文件。继续,直到你有一个文件。严重地?现在,假设您在一个文件中有 1000 万条记录,并且您一次读取 5000 条记录,您创建了多少临时文件以及获得最终版本需要多少时间?
  • 您通过获取两个最小的临时文件并将它们合并到一个较大的临时文件来进行合并。这会导致 log2(n) 倍于对内存中所有内容进行排序的文件 I/O 操作(n 是您开始使用的临时文件的数量)。因此,对于一开始的 8 个部分,这将是 300% 的 I/O 开销,对于 128 个部分,这将是 700%。
【解决方案3】:

正如其他提到的,您可以分步处理。
我想用我自己的话来解释这一点(在第 3 点上有所不同):

  1. 按顺序读取文件,在内存中一次处理 N 条记录(N 是任意的,取决于您的内存限制和您想要的临时文件的数量 T)。

  2. 对内存中的 N 条记录进行排序,将它们写入临时文件。在 T 上循环直到完成。

  3. 同时打开所有 T 个临时文件,但每个文件只读取一条记录。(当然,有缓冲区)。对于这些 T 记录中的每一个,找到较小的,将其写入最终文件,然后仅在该文件中前进。


优点:

  • 内存消耗尽可能低。
  • 与内存中的一切策略相比,您只执行 双倍磁盘访问。不错! :-)

数字示例:

  1. 包含 100 万条记录的原始文件。
  2. 选择拥有 100 个临时文件,因此一次读取和排序 10 000 条记录,然后将它们放入自己的临时文件中。
  3. 一次打开​​ 100 个临时文件,读取内存中的第一条记录。
  4. 比较第一条记录,写入较小的并推进这个临时文件。
  5. 在步骤 5 上循环一百万次。

已编辑

你提到了一个多线程应用程序,所以我想知道......

正如我们从这些关于此需求的讨论中看到的那样,使用更少的内存会降低性能,在这种情况下具有戏剧性的因素。所以我也可以建议只使用一个线程一次只处理一个排序,而不是作为多线程应用程序。

如果你处理 10 个线程,每个线程都有十分之一的可用内存,你的性能将会很糟糕,远低于初始时间的十分之一。如果你只使用一个线程,将其他 9 个请求排队并依次处理,你的全局性能会好得多,你会更快地完成这 10 个任务。


阅读此回复后: Sort a file with huge volume of data given memory constraint 我建议你考虑这种分布排序。在您的上下文中,这可能是巨大的收获。

对我的建议的改进是您不需要一次打开所有临时文件,您只需打开其中一个。它可以节省您的时间! :-)

【讨论】:

  • @Erika 嗯,这是一个例子,所以我们明白了。可以在临时文件大小和数量之间进行选择。
【解决方案4】:

尽管有你的限制,我还是会使用嵌入式数据库SQLITE3。和你一样,我每周处理 10-15 百万行平面文件,导入和生成排序数据非常非常快,你只需要一点免费的可执行文件 (sqlite3.exe)。例如:下载.exe 文件后,您可以在命令提示符下执行以下操作:

C:> sqlite3.exe dbLines.db
sqlite> create table tabLines(line varchar(5000));
sqlite> create index idx1 on tabLines(line);
sqlite> .separator '\r\n'
sqlite> .import 'FileToImport' TabLines

然后:

sqlite> select * from tabLines order by line;

or save to a file:
sqlite> .output out.txt
sqlite> select * from tabLines order by line;
sqlite> .output stdout

【讨论】:

  • 先插入然后创建索引可能更快。
【解决方案5】:

我会启动一个 EC2 集群并运行 Hadoop 的 MergeSort

编辑:不确定你想要多少细节,或者关于什么。 EC2 是亚马逊的弹性计算云——它允许您以低成本按小时租用虚拟服务器。这是他们的website

Hadoop 是一个开源 MapReduce 框架,专为并行处理大型数据集而设计。当作业可以拆分为可以单独处理然后合并在一起的子集时,它是 MapReduce 的一个很好的候选者,通常通过键排序(即分而治之的策略)。这是它的website

正如其他海报所提到的,外部排序也是一个很好的策略。我认为我在两者之间做出决定的方式取决于数据的大小和速度要求。一台机器可能会被限制为一次处理一个文件(因为您将耗尽可用内存)。因此,仅当您需要更快地处理文件时才考虑使用 EC2 之类的东西。

【讨论】:

  • 请详细说明?感谢您的回复。
【解决方案6】:

如果您的限制只是不使用外部数据库系统,您可以尝试使用嵌入式数据库(例如Apache Derby)。这样,您就可以在没有任何外部基础架构依赖的情况下获得数据库的所有优势。

【讨论】:

  • 您发现的任何不会对 VM 堆空间造成压力的解决方案都必须基于某种中间文件存储概念。所以基本上你开始实现你自己的数据库。因此,您可能只使用一个已知可以工作的现有的。
【解决方案7】:

您可以使用以下分而治之的策略:

创建一个函数 H(),它可以为输入文件中的每条记录分配一个编号。对于将在记录 r1 后面排序的记录 r2,它必须为 r2 返回一个比 r1 更大的数字。使用此功能将所有记录划分为适合内存的单独文件,以便您对它们进行排序。完成后,您可以将已排序的文件连接起来以获得一个大的已排序文件。

假设您有这个输入文件,其中每一行代表一条记录

Alan Smith
Jon Doe
Bill Murray
Johnny Cash

让我们构建 H() 以便它使用记录中的第一个字母,这样您可能会得到最多 26 个文件,但在本例中您只会得到 3 个:

<file1>
Alan Smith

<file2>
Bill Murray

<file10>
Jon Doe
Johnny Cash

现在您可以对每个单独的文件进行排序。这将交换 中的“Jon Doe”和“Johnny Cash”。现在,如果您只是连接 3 个文件,您将获得输入的排序版本。

请注意,您首先划分,然后才征服(排序)。但是,您确保以一种方式进行分区,即您需要排序的结果部分不会重叠,这将使合并结果更加简单。

实现分区函数 H() 的方法很大程度上取决于输入数据的性质。一旦你弄清楚了那部分,剩下的就变得轻而易举了。

【讨论】:

  • 我知道这是一个旧答案,但连接 3 个排序文件并不总是会导致排序版本。请读者不要认为这是一个有效的答案。
【解决方案8】:

这是一种无需大量使用 Java 内部排序且无需使用 DB 的方法。 假设:您有 1TB 空间并且文件包含或以唯一编号开头,但未排序

将文件分割N次。

一一读取这N个文件,每行/编号创建一个文件

用相应的编号命名该文件。命名时保持计数器更新以存储最少计数。

现在您已经可以将文件的根文件夹标记为按名称排序或暂停您的程序,以便您有时间在操作系统上触发命令以按名称对文件进行排序。您也可以以编程方式进行。

现在您有一个文件夹,其中包含按名称排序的文件,使用计数器开始一个一个地获取每个文件,在您的 OUTPUT 文件中输入数字,然后关闭它。

完成后,您将拥有一个带有排序数字的大文件。

【讨论】:

    【解决方案9】:

    我知道您提到过,无论多么轻量级都不要使用数据库……所以,也许这不是一个选择。但是,内存中的 hsqldb 呢……提交它,按查询排序,清除它。只是一个想法。

    【讨论】:

    • 我编写部署在生产服务器中的程序。该服务器由其他一些国家的其他团队处理。我没有直接访问服务器的权限!
    • 您不需要访问服务器...尝试使用嵌入式选项。在将数据从 1 db 迁移到另一个 db 时,我使用了嵌入式 hsqldb 来映射数据库 ID,但我无法维护我的原始 ID。效果非常好……性能出奇的好。
    • 但如果您仅在内存中使用嵌入式数据库,则数据仍需要适合可用内存。我确信 hsqldb 可以使用临时存储文件,这样它仍然可以工作。只是想指出,不能完全在内存中运行。
    【解决方案10】:

    您可以使用 SQL Lite 文件 db,将数据加载到 db,然后让它为您排序并返回结果。 优点:不用担心写出最好的排序算法。 缺点:您将需要磁盘空间,处理速度较慢。 https://sites.google.com/site/arjunwebworld/Home/programming/sorting-large-data-files

    【讨论】:

      【解决方案11】:

      您可以只使用两个临时文件 - 源文件和目标文件 - 以及尽可能少的内存。 第一步,您的源文件是原始文件,最后一步,目标文件是结果文件。

      每次迭代:

      • 从源文件中读入一个滑动缓冲区一半大小的数据块;
      • 对整个缓冲区进行排序
      • 将缓冲区的前半部分写入目标文件。
      • 将缓冲区的后半部分移到开头并重复

      保留一个布尔标志,表明您是否必须在当前迭代中移动某些记录。 如果标志仍然为假,则您的文件已排序。 如果引发,请使用目标文件作为源重复该过程。

      最大迭代次数:(文件大小)/(缓冲区大小)*2

      【讨论】:

      • 如果我正确理解了这个算法,它就是排序组的某种冒泡排序。假设数据大小为P*K,其中 K 作为缓冲区大小,那么复杂度大致为P^2 K log K,在 P 中是二次方。如果 P 很大,则最好使用其他答案中显示的合并排序策略(就像 KLE 的那个),即使你不得不牺牲一些局部性(磁盘缓存到底有多好?)。
      【解决方案12】:

      可以下载适用于 windows 的 gnu sort:http://gnuwin32.sourceforge.net/packages/coreutils.htm 即使这使用太多内存,它也可以合并较小的排序文件。它会自动使用临时文件。

      在 cmd.exe 中还有 Windows 附带的排序。这两个命令都可以指定要排序的字符列。

      【讨论】:

        【解决方案13】:

        大文件https://github.com/lianzhoutw/filesort/的文件排序软件。 它基于文件合并排序算法。

        【讨论】:

        • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
        【解决方案14】:

        如果您可以在文件中向前/向后移动(搜索),并重写文件的某些部分,那么您应该使用bubble sort

        您将不得不扫描文件中的行,并且此时只需要在内存中有 2 行,如果它们的顺序不正确,则将它们交换。重复此过程,直到没有要交换的文件。

        【讨论】:

        • 在文件中交换行将需要您在要交换的两行之间重写整个文件。当然,除非您对具有固定行长的文件进行排序。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多