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

Java 如何在有限的内存中处理大量的小文件?,java,multithreading,java.util.concurrent,Java,Multithreading,Java.util.concurrent,以下是问题的描述: 我在一个目录中有大量的小日志文件,假设: 所有文件都遵循命名约定:yyyy-mm-dd.log,例如:2013-01-01.log、2013-01-02.log 大约有1000000个小文件 所有文件的总大小为数TB 现在,我必须为每个文件中的每一行预先准备一个行号,行号是累积的,分布在文件夹中的所有文件中(文件按时间戳排序)。例如: 2013-01-01.log中,行号为1~2500 2013-01-02.log,行号从2501到7802 2016-03-26.log,

以下是问题的描述:

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

  • 所有文件都遵循命名约定:
    yyyy-mm-dd.log
    ,例如:2013-01-01.log、2013-01-02.log
  • 大约有1000000个小文件
  • 所有文件的总大小为数TB
  • 现在,我必须为每个文件中的每一行预先准备一个行号,行号是累积的,分布在文件夹中的所有文件中(文件按时间戳排序)。例如:

    • 2013-01-01.log中,行号为1~2500
    • 2013-01-02.log,行号从2501到7802
    • 2016-03-26.log,行号从1590321~3280165
    将覆盖所有文件以包含行号

    限制条件包括:

  • 存储设备是SSD,可以同时处理多个IO请求
  • CPU足够强大
  • 您可以使用的总内存为100MB
  • 尽量提高应用程序的性能
  • 用Java实现和测试

  • 经过思考和寻找,这里是我想到的最好的。有点 很长,所以我只简单介绍一下每一步:

  • 同时计算每个文件的行数,并将映射保存到
    ConcurrentSkipListMap
    ,键是文件名,值是文件的行数,键按顺序排列

  • 通过遍历
    ConcurrentSkipListMap
    计算每个文件的起始行号,例如,2013-01-01.log的起始行号和行号分别为1和1500,则2013-01-02.log的起始行号为1501

  • 为每个文件的每一行预加行号:使用
    BufferedReader
    逐行读取每个文件,预加行号,然后使用
    BufferedWriter
    写入相应的tmp文件。创建线程池并并发处理

  • 同时使用线程池将所有tmp文件重命名回原始名称

  • 我已经在我的MBP上测试了这个程序,步骤1和步骤3是预期的瓶颈。
    您是否有更好的解决方案,或者对我的解决方案进行优化?提前谢谢

    不确定这些问题是否符合问答的SO模式,但我尝试了一些提示来回答

    事实1)考虑到1M文件和100MB的限制,几乎没有办法同时在内存中保存所有文件的信息。除了像以前我们用C语言编程时那样,可能会做很多小动作

    事实2)我看不到一种方法可以绕过一次读取所有文件来计算行号,然后全部重写,这意味着再次读取所有文件

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

    B) 如果有一个惰性的
    迭代器
    ,无论是从jdk列出文件还是从自我实现生成文件名,都可以使用N个迭代器将工作划分为N个线程

    C) 现在,每个线程负责自己的文件切片,读取它们并只保留其切片的总行数

    D) 根据每个切片的总数,计算每个切片的起始编号

    E) 再次在N个线程上分发迭代器以进行行编号。在编写tmp文件后立即重命名该文件,不要等待所有操作完成,以免再次迭代所有文件

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


    EDIT:this
    Files.find()
    是惰性填充的,但我无法轻松找到它背后的代码(Java 8中的一些
    DirectoryStream
    ),以查看懒散是否只涉及一次读取一个文件夹的全部内容,或者是否确实一次读取一个文件名。或者这是否取决于所使用的文件系统。

    对于100MB限制和1M文件,第34行可能已经在
    logPath.toFile().listFiles()处爆炸了。100 MB意味着如果你胆敢同时在内存中保留所有文件的信息,那么每个文件只能使用100字节。@Harald,谢谢。可能
    logPath.toFile().list()
    会消耗更少的内存。另外,有人建议在Java7中使用
    Files.walkFileTree
    。我将尝试这两种方法,但问题是我无法创建这么多测试日志。A)这是一个真正问题的家庭作业,文件名可能不连续,很难根据需要生成。B) 事实上,我和你的想法是一样的。E) 我不会在编写tmp文件后立即重命名它,因为我会让它稍后同时完成,我希望它会更快。谢谢你,哈拉尔德!