Java 中使用哪个 API 来读取文件才能获得最佳性能?
-
06-07-2019 - |
题
在我工作的地方,过去每个文件的行数超过一百万行。尽管服务器内存超过 10GB,其中 JVM 内存为 8GB,但有时服务器会挂起片刻并阻塞其他任务。
我分析了代码,发现文件读取时内存使用量频繁增加千兆字节(1GB 到 3GB),然后突然恢复正常。看来这种频繁的高内存和低内存使用挂起了我的服务器。当然,这是由于垃圾收集造成的。
我应该使用哪个 API 来读取文件以获得更好的性能?
现在我正在使用 BufferedReader(new FileReader(...))
读取这些 CSV 文件。
过程 :我如何读取该文件?
- 我逐行读取文件。
- 每行都有几列。根据我相应地解析它们的类型(双精度型成本列、int 型访问列、String 型关键字列等)。
- 我将符合条件的内容(访问 > 0)推送到 HashMap 中,并最终在任务结束时清除该 Map
更新
我读取 30 或 31 个文件(一个月的数据)并将符合条件的文件存储在地图中。后来这张地图被用来在不同的表中找到一些罪魁祸首。因此,读取是必须的,存储数据也是必须的。虽然我现在已经将 HashMap 部分切换到 BerkeleyDB,但是读取文件时的问题是相同的甚至更糟。
解决方案
BufferedReader 是用于此目的的两个最佳 API 之一。如果您确实在文件读取方面遇到困难,另一种选择可能是使用 蔚来 对文件进行内存映射,然后直接从内存中读取内容。
但你的问题不在于读者。您的问题是,每次读取操作都会创建一堆新对象,很可能是您在读取后立即执行的操作。
您应该考虑清理输入处理,着眼于减少创建的对象的数量和/或大小,或者在不再需要时更快地删除对象。是否可以一次处理一行或一大块文件,而不是将整个文件吸入内存进行处理?
另一种可能性是摆弄垃圾收集。你有两种机制:
每隔一段时间显式调用一次垃圾收集器,例如每 10 秒或每 1000 个输入行或其他。这将增加 GC 完成的工作量,但每次 GC 花费的时间会更少,您的内存不会膨胀太多,因此希望对服务器其余部分的影响较小。
摆弄 JVM 的垃圾收集器选项。这些在 JVM 之间有所不同,但是
java -X
应该给你一些提示。
更新: 最有前途的方法:
您真的需要一次性将整个数据集放入内存中进行处理吗?
其他提示
我分析了代码并发现了 文件读取内存使用量上升 频繁的千兆字节(1GB到3GB)和 然后突然恢复正常。它 似乎这种频繁的高低 内存使用挂起我的服务器。的 当然这是由于垃圾 集合。
使用 BufferedReader(new FileReader(...))
不会导致这种情况。
我怀疑问题是您正在将行/行读入数组或列表,处理它们然后丢弃数组/列表。这将导致内存使用量增加然后再次减少。如果是这种情况,您可以通过在读取时处理每一行/每行来减少内存使用量。
编辑:我们同意问题是关于用于表示内存中文件内容的空间。大内存哈希表的替代方案是返回旧的“排序合并”。计算机内存以千字节为单位时使用的方法。 (我假设处理由一个步骤决定,你用键K进行查找以获得相关的行R.)
-
如有必要,预处理每个输入文件,以便可以在密钥K上对其进行排序。
-
使用高效的文件排序实用程序将所有输入文件按顺序排序到K.您希望使用将使用经典合并排序算法的实用程序。这将 将每个文件拆分为可以在内存中排序的较小块,对块进行排序,将它们写入临时文件,然后合并已排序的临时文件。 UNIX / Linux
sort
实用程序是一个不错的选择。 -
并行读取已排序的文件,从所有文件中读取与每个键值相关的所有行,处理它们,然后单步执行下一个键值。
醇>
实际上,我对使用BerkeleyDB没有帮助感到有些惊讶。但是,如果分析告诉您构建数据库的时间很长,那么在构建数据库之前,您可以通过将输入文件(如上所述!)排序为升序键来加快速度。 (当创建基于文件的大型索引时,如果按键顺序添加条目,则性能会更好。)
尝试使用以下vm选项来调整gc(并执行一些gc打印):
-verbose:gc -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:+PrintGCDetails -XX:+PrintGCTimeStamps