【问题标题】:Scala - Iterator over all the lines in the files in a directoryScala - 遍历目录中文件中所有行的迭代器
【发布时间】:2012-04-10 22:08:45
【问题描述】:

我真的很喜欢

for (line <- Source fromFile inputPath getLines) {doSomething line}

在 scala 中迭代文件的构造,我想知道是否有一种方法可以使用类似的构造来迭代目录中所有文件中的行。

这里的一个重要限制是所有文件加起来的空间量会导致堆溢出。 (想想几十 GB,所以增加堆大小不是一种选择)作为暂时的解决方法,我一直在将每个文件放在一个文件中,并使用上面的结构,它适用于 b/c 的懒惰。

重点是,这似乎引发了诸如.. 我可以连接两个(一百个)惰性迭代器并获得一个非常大、非常惰性的迭代器吗?

【问题讨论】:

    标签: scala io lazy-evaluation


    【解决方案1】:

    是的,虽然不是那么简洁:

    import java.io.File
    import scala.io.Source
    
    for {
      file <- new File(dir).listFiles.toIterator if file.isFile
      line <- Source fromFile file getLines
    } { doSomething line }
    

    诀窍是flatMapits for-comprehension syntactic sugar。例如,上面的内容或多或少等同于以下内容:

    new File(dir)
      .listFiles.toIterator
      .filter(_.isFile)
      .flatMap(Source fromFile _ getLines)
      .map(doSomething)
    

    正如 Daniel Sobral 在下面的评论中指出的那样,这种方法(以及您问题中的代码)将使文件保持打开状态。如果这是一次性脚本,或者您只是在 REPL 中工作,这可能没什么大不了的。如果确实遇到问题,可以使用pimp-my-library pattern 来实现一些基本的资源管理:

    implicit def toClosingSource(source: Source) = new {
      val lines = source.getLines
      var stillOpen = true
      def getLinesAndClose = new Iterator[String] {
        def hasNext = stillOpen && lines.hasNext
        def next = {
          val line = lines.next
          if (!lines.hasNext) { source.close() ; stillOpen = false }
          line
        }
      }
    }
    

    现在只需使用Source fromFile file getLinesAndClose,您就不必担心文件处于打开状态。

    【讨论】:

    • 太完美了,我刚刚使用 scala repl 运行了大约 10gb 的文件,并带有基于此的代码位,并且内存使用量几乎没有变化。非常感谢!
    • 但请注意,每个文件的Source 并未关闭。在这种特殊情况下,代码可能涉及数百个文件,因此使用某种 ARM 很重要。
    猜你喜欢
    • 2015-03-05
    • 1970-01-01
    • 1970-01-01
    • 2018-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-12
    • 2017-06-22
    相关资源
    最近更新 更多