我最近开始使用 Scala 并遇到以下问题。下面是 4 种不同的方法来遍历文件的行,做一些事情,并将结果写入另一个文件。其中一些方法正如我所想的那样工作(尽管使用大量内存来这样做),而有些则无休止地消耗内存。
这个想法是将 Scala 的 getLines Iterator 包装为一个 Iterable。我不在乎它是否多次读取文件——这就是我期望它做的事情。
这是我的复制代码:
class FileIterable(file: java.io.File) extends Iterable[String] {
override def iterator = io.Source.fromFile(file).getLines
}
// Iterator
// Option 1: Direct iterator - holds at 100MB
def lines = io.Source.fromFile(file).getLines
// Option 2: Get iterator via method - holds at 100MB
def lines = new FileIterable(file).iterator
// Iterable
// Option 3: TraversableOnce wrapper - holds at 2GB
def lines = io.Source.fromFile(file).getLines.toIterable
// Option 4: Iterable wrapper - leaks like a sieve
def lines = new FileIterable(file)
def values = lines
.drop(1)
//.map(l => l.split("\t")).map(l => l.reduceLeft(_ + "|" + _))
//.filter(l => l.startsWith("*"))
val writer = new java.io.PrintWriter(new File("out.tsv"))
values.foreach(v => writer.println(v))
writer.close()
它正在读取的文件约为 10GB,行数为 1MB。
前两个选项使用恒定的内存量(~100MB)迭代文件。这是我所期望的。这里的缺点是迭代器只能使用一次,并且它使用 Scala 的按名称调用约定作为伪迭代。(作为参考,等效的 c# 代码使用 ~14MB)
第三个方法调用定义在 TraverableOnce 中的 toIterable。这个工作,但它使用大约 2GB 来做同样的工作。不知道内存的去向,因为它无法缓存整个 Iterable。
第四个是最令人震惊的——它立即使用所有可用内存并抛出 OOM 异常。更奇怪的是,它对我测试过的所有操作都执行此操作:drop、map 和 filter。查看实现,它们似乎都没有保持太多状态(尽管下降看起来有点可疑 - 为什么它不只计算项目?)。如果我不做任何操作,它工作正常。
我的猜测是,它在某处维护对所读取的每一行的引用,尽管我无法想象如何。在 Scala 中传递 Iterables 时,我看到了相同的内存使用情况。例如,如果我采用案例 3 (.toIterable) 并将其传递给将 Iterable[String] 写入文件的方法,我会看到同样的爆炸。
有任何想法吗?