您需要自定义将字段解析为更小的、更紧凑的类型。如果您正在存储大量的重复字符串,请使用字符串规范化的方法重新使用重复的字符串(共享引用)。通常的做法是在解析时使用字符串池来共享引用。
我在这里发布了一个简短的演示项目这里,它以两种方式实现了这个功能:
- 使用 spork.util.table(我旧的表格处理库),它基于持久化结构并使用我提到的技术。
- 使用 tech.ml.dataset,这是最近的一个尝试,它利用 TableSaw 表实现在内存高效的结构(可变,但 COW 实现用于持久语义)。
这两种解决方案都能轻松处理默认堆(约 2gb)下的 300mb tsv,尽管 tech.ml.dataset 在实际应用中显著更节省内存。
我还将一个版本放在测试中,该版本可以复制您的测试输入(在空项的数量上,我猜测了 1/3)。它展示了使用 spork、tech.ml(当前在尝试解析日期时失败)以及最后使用纯 clojure 函数手动处理的方法。如果您希望最小化内存占用(以及性能),那么解析为原始数据类型是必需的。不幸的是,Java 集合通常是装箱的(除了数组之外),因此您需要类似 FastUtils 的东西或其他原始数据支持的集合。Clojure 的原始数据支持向量有些帮助,但它们仍然因为数组的 trie 而产生引用开销(数组只在树叶)。或者,如果您只是从文件中构建同构类型的密集数组,您可以构建一个原始数组并填充它。这将空间高效,技术上兼容,尤其是如果您使用这些浮点数进行数值运算并且可以利用密集格式。
结论是,直观解析,尤其是当保留字符串或装箱对象的引用时,将需要庞大的堆,因为 Java 对象很耗内存。正如 Andy 所提到的,较新的 JVM 可以通过压缩字符串(这在 JVM 级别真的很好)在一定程度上缓解这种情况,与字符串规范化有相同的效果。
注意:这仅适用于您必须“保留”数据的情况……如果您可以对其降维或以其他方式进行流式计算,那么对于任意数据集(例如,大于内存的数据集)的直观解析可能不足以破坏堆,因为 JVM 将在您处理完旧引用后立即进行垃圾回收。
还有一些其他用于此类任务的有用库 iota,
以及最近的通过 libpython-clj 的 pandas 包装器 panthera。
关于内存性能的说明:JVM默认不会将内存释放回系统,因此它看起来会“增长”到由-Xmx指定的限制(我认为默认是2GB),并且它会停留在那里(即使在GC周期之后)。如果您想了解实际使用的是什么与预留的是什么,您需要连接一个分析器并查看堆使用统计信息(如jvisualvm)。