Scala Source.fromFile内存消耗

时间:2016-04-18 20:06:58

标签: java scala csv io heap

所有

我的CSV文件只有~120MB(称之为demo.csv

以下代码导致堆从正常大小100MB升级到1.7GB,尽管加载的基础数据仅为120MB

我能在这里做得更好吗?

case class Foo(x:String, y: Array[String])
....
val src = Source.fromFile(file)
val lines = src.getLines()
val raw = lines.map(_.split(",")).toArray
src.close()

/**
  * a map from accountId to their benchmark components
  */
val result = raw.groupBy(_.(0)).map {
  case (x, y) => Foo(x,y)
}.toArray

我知道toArray可能是这里的问题,但是我确实需要groupBy ...并且除非我将所有内容都记入内存,否则无法访问它。有什么替代方案?

据我所知,在groupBytoArray阶段,堆可能会暂时膨胀。但由于底层数据只有120MB,我的堆可能会永久地升>1G? (换句话说,任何被保留的东西似乎都不是GC编辑的)

2 个答案:

答案 0 :(得分:3)

首先,我建议使用专用的CSV解析库 - 手动解析CSV比它看起来复杂得多,有很多边缘情况(如果你的一个值包含换行符,说?)。我们会跟kantan.csv一起去,因为我是作者,但那里有很多高质量的图书馆。

我们要做的是:

  • Iterator[(String, String)]
  • 打开文件
  • 折叠该迭代器,构建Map[String, List[String]],其中密钥是帐户ID和值基准数据。
  • 如果您真的热衷于该Foo案例类,请将地图转换为该列表。

没有进一步的麻烦:

import kantan.csv._     // kantan.csv core types.
import kantan.csv.ops._ // syntax.

case class Foo(id: String, data: List[String])

// Open the CSV file for reading, assuming ; as column separator
// and no header row.
input.asUnsafeCsvReader[(String, String)](';', false)

// Fold on the file, aggregating data in a map
  .foldLeft(Map.empty[String, List[String]]) { case (acc, (key, value)) =>
    acc + (key -> (value :: acc.getOrElse(key, List.empty)))

// Now that we have the whole data as a Map, turn that into a List[Foo].
  }.map(r => Foo(r._1, r._2))

这永远不会多次加载输入数据,一旦放入聚合图中就丢弃每一行 - 而不是你的实现,如果我正在计数,它在内存中有4次到最后(一次为直线,一次为分割线,一次为List[Foo],一次为Array[Foo])。

此外,当你没有选择时,字符串是好的,但如果你有更好的类型 - 整数,比如说或日期 - 请改用它们。 int使用比其字符串表示少得多的内存。

让我知道结果如何!

答案 1 :(得分:2)

当您将典型文件读入内存时,会自动将大小加倍,因为这会将单字节字符表示转换为双字节JVM字符。然后,由于Oracle在Java 7的点发行版中进行了更改,当您将输入拆分为子字符串时(在所述更改子字符串引用原始字符串的后备数组之前),您再次加倍,但这导致了 - - 广泛使用的Glassfish,因此Oracle更改了JVM的行为,将子字符串字符复制到新数组;因为您仍然可以引用原始字符串以及子字符串双内存使用)。

根据分割字符串的长度,你可能只是内存使用量的两倍 - 每个字符串占用大约40个字节的内存,超过字符表示中的实际字节数,这是由于String对象本身和用于字符的数组对象。

所以我猜这会让你占用1.7GB的一半。剩下的可能是由于raw.groupBy语句中创建的临时结构,但我预计其中大部分都会在之后发布。

在检查内存使用情况之前,您是否在做一些延迟?通常需要这样才能进行垃圾收集。垃圾收集完成后,您应该能够合理估计实际内存使用情况,如runtime.totalMemory() - runtime.freeMemory()。