您需要自定义地将字段解析为更小、更紧凑的类型。如果您存储了大量重复的字符串,请使用字符串规范化解引用重复字符串(共享引用)。常见的做法是在解析时使用 字符串池 来共享引用。
我在这里发表了一个简短的项目示例 这里,介绍两种处理方式
- 使用 spork.util.table(我的旧表变形库),它是基于持久结构并使用了我提到的技术的
- 使用 tech.ml.dataset,这是一项近期工作,它利用了TableSaw表实现快速、高效的内存结构(可变,但COW实现持久语义)。
这两种解决方案都能轻松处理一个300MB的TSV文件,尽管默认堆大小(约2GB),但在实际使用中,tech.ml.dataset的内存效率要高得多。
我还把一个版本放在了 这里,用于复制您的测试输入(在一定程度上我猜测了有多少空项,占1/3)。它展示了使用spork、tech.ml(当前尝试解析日期时失败)的方法,最后使用plain clojure函数手动处理。如果您想要最小的内存占用(以及性能),则解析到原语是必需的。但不幸的是,Java集合通常被装箱(除了数组之外),因此您需要像FastUtils或其他基于原语的集合。Clojure基于原语的向量有所帮助,但它们仍然因为数组的trie而承担着引用开销(数组仅位于叶子层)。或者,如果您只是从文件中构建具有相同类型的密集数组,则可以构造一个原语数组并填充它。这将是非常有效的空间利用,并且与机器非常融洽,尤其是当您使用这些浮点数进行数字运算并可以利用密集格式时。
总的来说——原始解析,尤其是当保留对字符串或装箱对象的引用时——将需要巨大的堆,因为Java对象很“吃”内存。正如Andy所说,新的JVM可以通过压缩字符串(这实际上与字符串规范化解引用相同,但在JVM级别上非常好)在一定程度上解决这个问题。
注意:这仅适用于您必须“保留”数据......如果您可以遍历或以其他方式流计算,则可以避开快速解析任意数据集(例如大于内存的大型数据集)而不会有堆溢出,因为JVM会在您处理后立即回收旧引用。
还有一些用于此类任务的库非常有用 iota,
以及最近通过libpython-clj包装的pandas, panthera。
关于内存性能的注释:JVM默认不会将内存释放回操作系统,因此其内存使用似乎会“增长”到由-Xmx指令指定的限制(默认为2GB),并且似乎会停留在那里(甚至经过垃圾回收周期)。如果您想了解实际使用与保留之间的差异,则需要附加一个分析器并查看堆使用统计信息(例如jvisualvm)。