如何处理有限内存中的大量小文件?

时间:2016-03-26 14:55:53

标签: java multithreading java.util.concurrent

以下是问题的描述:

我在目录中有大量的小日志文件,假设:

  1. 所有文件都遵循命名约定:yyyy-mm-dd.log,例如:2013-01-01.log,2013-01-02.log。
  2. 大约有1,000,000个小文件。
  3. 所有文件的总大小为几TB。
  4. 现在我必须为每个文件中的每一行添加一个行号,并且行号是累积的,在文件夹中的所有文件(文件按时间戳排序)之间传播。例如:

    • 在2013-01-01.log中,行号从1~2500
    • 于2013-01-02.log,行号从2501~7802
    • ...
    • 于2016-03-26.log,行号从1590321~3280165

    覆盖所有文件以包含行号。

    约束是:

    1. 存储设备是SSD,可以同时处理多个IO请求。
    2. CPU足够强大。
    3. 您可以使用的总内存为100MB。
    4. 尝试最大化应用程序的性能。
    5. 用Java实现和测试。
    6. 经过思考和搜索,这是我所想到的最好的solutionThe code有点儿 很长,所以我只简单介绍一下每一步:

      1. 同时计算每个文件的行数并将映射保存到ConcurrentSkipListMap,密钥是文件名,值是文件的行数,密钥是有序的

      2. 通过遍历ConcurrentSkipListMap计算每个文件的起始行号,例如,2013-01-01.log的起始行号和行数分别为1和1500,然后开始2013-01-02.log的行号是1501。

      3. 在每个文件的每一行前面添加行号:使用BufferedReader逐行读取每个文件,添加行号,然后使用BufferedWriter写入相应的tmp文件。创建一个线程池并同时处理。

      4. 使用线程池同时将所有tmp文件重命名为原始名称。

      5. 我已经在我的MBP上测试了该程序,步骤1和步骤3是预期的瓶颈。 您有更好的解决方案,或者我的解决方案的一些优化?提前谢谢!

1 个答案:

答案 0 :(得分:1)

不确定这些问题是否符合Q& A的SO模型,但我尝试了一些答案。

事实1)鉴于1M文件和100MB限制,几乎没有办法同时保存内存中所有文件的信息。除了可能通过像我们在C中编程的过去那样做一些小小的摆弄。

事实2)我没有办法绕过读取所有文件一次来计算行号,然后重写所有文件,这意味着再次阅读它们。

A)这是一个功课问题吗?可能有一种方法可以在Java 7或8中一个接一个地从一个文件夹中生成文件名,但我不知道它。如果有,请使用它。如果没有,您可能需要生成文件名而不是列出它们。这将要求您可以插入开始日期和结束日期作为输入。不确定这是否可行。

B)鉴于存在一个惰性Iterator<File>,无论是从jdk到列表文件还是自我实现来生成文件名,都要获得N个来将工作划分为N个线程。

C)现在每个线程都会处理它的文件片段,读取它们并仅保留其片段的总行数。

D)从每个切片的总计算出每个切片的起始编号。

E)再次在N个线程上分配迭代器以进行行编号。在写入tmp文件后立即重命名,不要等待所有内容完成,不必再次遍历所有文件。

在每个时间点,内存中保存的信息都很小:每个线程一个文件名,整个切片上的行数,正在读取的文件的当前行。如果N不是非常大,那么100MB就足够了。

编辑:Some say Files.find()已经懒洋洋地填充,但我无法轻易找到它背后的代码(Java 8中的一些DirectoryStream),看看懒惰是否只与读取相关一次一个文件夹的完整内容,或者是否确实一次读取一个文件名。或者这是否取决于所使用的文件系统。