【发布时间】:2016-08-28 16:17:52
【问题描述】:
在我的项目中,我们需要读取一个非常大的文件,其中每一行都有由特殊字符(“|”)分隔的标识符。不幸的是,我不能使用并行性,因为有必要在一行的最后一个字符与下一行的第一个字符之间进行验证,以决定是否将其提取。无论如何,要求非常简单:将行分成标记,分析它们并仅将其中一些存储在内存中。代码很简单,如下所示:
final LineIterator iterator = FileUtils.lineIterator(file)
while(iterator.hasNext()){
final String[] tokens = iterator.nextLine().split("\\|");
//process
}
但是这段代码非常非常低效。方法 split() 生成了太多没有被收集的临时对象(最好在这里解释:http://chrononsystems.com/blog/hidden-evils-of-javas-stringsplit-and-stringr。
出于比较目的:一个 5mb 的文件在文件处理结束时使用了大约 35 mb 的内存。
我测试了一些替代方案,例如:
- 使用预编译模式 (Performance of StringTokenizer class vs. split method in Java)
- 使用 Guava 的拆分器 (Java split String performances)
- 优化字符串存储 (http://java-performance.info/string-packing-converting-characters-to-bytes/)
- 使用优化的集合 (http://blog.takipi.com/5-coding-hacks-to-reduce-gc-overhead)
但它们似乎都没有足够的效率。使用 JProfiler,我可以看到临时对象使用的内存量太大(使用了 35 mb,但有效对象仅使用了 15 mb)。
然后我决定做一个简单的测试:读取 50,000 行后,显式调用 System.gc()。然后,在进程结束时,内存使用量从 35 mb 减少到 16 mb。我测试了很多很多次,总是得到相同的结果。
我知道调用 System.gc () 是一种不好的做法(如Why is it bad practice to call System.gc()? 所示)。但是在 split() 方法可以被调用数百万次的场景中,还有其他选择吗?
[更新] 我使用 5 mb 的文件仅用于测试目的,但系统应该处理更大的文件(500Mb ~ 1Gb)
【问题讨论】:
-
“split() 方法生成了太多没有被收集的临时对象(这里最好解释:chrononsystems.com/blog/…。” 太糟糕了,它没有解释什么你声称。目前还不清楚为什么要拆分字符串,而不是解析它。
-
你接受或拒绝
tokens的元素的标准是什么? -
显而易见的其他解决方案是不拆分字符串,而是原位扫描/解析/处理字符串。
-
即使使用35mb,真的有关系吗?如果您的 JVM 没有那么多内存,它无论如何都会尝试在两者之间收集,如果有,那么为什么还要麻烦呢?最后它最终会收集。
-
35 MB 和 16 MB 之间的差异大约值 10 美分。尝试节省 10 美分的内存需要多少时间?最低工资约为 1 分钟。一般来说,不要调用System.gc(),让JVM在需要的时候去做。
标签: java string performance split garbage-collection