您需要将字段自定义解析为更小、更紧凑的类型。如果您存储了一堆重复的字符串,请使用字符串规范(String canonicalization)以复用重复的字符串(共享引用)。实现这一功能的典型方法是在解析时使用 字符串池(string pools) 来共享引用。
我在这里发布了一个简短的项目演示 (链接) ,它在这方面采用了两种方法。
- 使用 spork.util.table(我老式的表格调整库),它基于持久结构并使用了我所提到的技术。
- 使用 tech.ml.dataset,这是最近的一项努力,它利用了 TableSaw 表实现,以实现快速、内存效率高的结构(可变的,但使用 COW 实现持久语义)。
这两种解决方案都能轻松处理大约 300MB 的 tsv 文件,尽管使用默认堆(大约 2GB),而 tech.ml.dataset 在实际中要显著节省内存。
我还将一个版本放在了 (链接) ,该版本复制了您的测试输入(在一定程度上,我在空字符串的数量上猜测了1/3)。它显示了使用 spork、tech.ml(目前尝试解析日期时失败)的方法,最后是使用普通的 Clojure 函数手动解析。如果您想要最小的内存占用(以及性能),那么将数据解析为原始类型是必需的。不幸的是,Java 集合通常是封装的(除了数组外),因此您需要像 FastUtils 或其他原始支持集合一样的东西。Clojure 原始背景向量有点帮助,但它们仍然由于数组(数组只有叶子)的 trie 而产生引用开销。或者,如果您只是从文件中构建一个同质类型的密集数组,您可以直接构建一个原始数组并填充它。这将非常节省空间,并且与机械操作更和谐,尤其是如果您使用这些浮点数进行数值计算并可以利用密集格式。
总之,-朴素解析,尤其是当保留对字符串或封装对象的引用时——将需要过多的堆,因为 Java 对象内存占用很大。正如 Andy 所说,较新的 JVM 可以通过压缩字符串(它在 JVM 层面上与字符串规范执行相同的作用,但非常好)稍微缓解这个问题。
注意:这仅适用于您必须“保留”数据的情况……如果您可以对其应用 Reduce 或其他方式进行流式计算,则可以无需打破堆,轻松地使用朴素解析处理任意数据集(例如,大于内存的数据集),因为 JVM 会在您处理旧引用后立即进行垃圾回收。
还有一些其他有用的库可用于此类任务 iota ,
以及最近通过 libpython-clj 的小 pandas 封装 panthera。
内存性能说明:JVM 默认不会将内存释放回系统,因此它看起来会“增长”到由 -Xmx 设置的限制(我认为默认是 2GB),并且它看起来会停留在那里(甚至在垃圾回收周期之后)。如果您想了解实际使用和保留的内存情况,您需要附加一个分析器并查看堆使用统计信息(如 jvisualvm)。