【问题标题】:Read entire file in Scala?在 Scala 中读取整个文件?
【发布时间】:2010-11-20 01:02:45
【问题描述】:

在 Scala 中将整个文件读入内存的简单而规范的方法是什么? (理想情况下,可以控制字符编码。)

我能想到的最好的是:

scala.io.Source.fromPath("file.txt").getLines.reduceLeft(_+_)

或者我应该使用Java's god-awful idioms 之一,其中最好的(不使用外部库)似乎是:

import java.util.Scanner
import java.io.File
new Scanner(new File("file.txt")).useDelimiter("\\Z").next()

通过阅读邮件列表讨论,我不清楚 scala.io.Source 是否应该是规范的 I/O 库。我不明白它的预期目的究竟是什么。

...我想要一些简单易记的东西。例如,在这些语言中,很难忘记成语...

Ruby    open("file.txt").read
Ruby    File.read("file.txt")
Python  open("file.txt").read()

【问题讨论】:

  • 如果您知道正确的工具,Java 并没有那么糟糕。导入 org.apache.commons.io.FileUtils; FileUtils.readFileToString(new File("file.txt", "UTF-8")
  • 这条评论忽略了语言设计的重点。因此,任何具有用于您想要执行的操作的简单库函数的语言都与其函数调用语法一样好。给定一个无限且 100% 记忆的库,所有程序都将通过一个函数调用来实现。当一种编程语言需要较少的预制组件来实现特定结果时,它是很好的。
  • 恐怕“给定一个无限且 100% 记忆的库”不是任何理性论证的前提!编程语言是为人类设计的,理想情况下应该只包含将事物粘合在一起所需的抽象
  • 最好的现代解决方案是使用李的os-libas he mentioned here。 os-lib 隐藏了 Java 的丑陋,提供了Ruby-like elegance

标签: scala


【解决方案1】:
val lines = scala.io.Source.fromFile("file.txt").mkString

顺便说一句,“scala.”并不是必需的,因为它始终在范围内,当然,您可以全部或部分导入 io 的内容,而不必在前面加上“io”。也是。

但是,以上内容使文件处于打开状态。为避免出现问题,您应该像这样关闭它:

val source = scala.io.Source.fromFile("file.txt")
val lines = try source.mkString finally source.close()

上面代码的另一个问题是,由于其实现性质,它非常慢。对于较大的文件,应该使用:

source.getLines mkString "\n"

【讨论】:

  • 我来晚了,但我讨厌人们不知道他们可以在后备箱中执行 "io.File("/etc/passwd").slurp"。跨度>
  • @extempore 如果你真的认为我吃力不讨好,我真的很抱歉。我非常感谢您对 Scala 语言的支持,以及每次您亲自调查我提出的问题、为我遇到的问题提出解决方案或向我解释某些内容时。那么,我将借此机会感谢您将 scala.io 变成了体面和有价值的东西。从现在开始,我会更加直言不讳地表示感谢,但我仍然讨厌这个名字,对不起。
  • "slurp" 多年来一直是 Perl 中一次读取整个文件的名称。 Perl 的命名传统比 C 语言家族更发自内心和非正式的命名传统,有些人可能会觉得这很反感,但在这种情况下,我认为它很合适:对于丑陋的实践来说,这是一个丑陋的词。当你 slurp() 时,你知道你在做一些顽皮的事情,因为你只需要输入它。
  • File.read() 会是一个更好的名字,并且与 Ruby 和 Python 保持一致。
  • @extempore:你无法阻止人们厌恶。就是这样。有些人不喜欢你所做的每一个选择,这不应该打扰你。这就是生活,你不能取悦所有人:)
【解决方案2】:
// for file with utf-8 encoding
val lines = scala.io.Source.fromFile("file.txt", "utf-8").getLines.mkString

【讨论】:

  • 在原始答案中添加“getLines”将删除所有换行符。应该是 "Source.fromFile("file.txt", "utf-8").mkString"。
  • 另请参阅我在 Daniel C. Sobral 的回答中的评论 - 这种用法不会关闭 Source 实例,因此 Scala 可能会保留对文件的锁定。
【解决方案3】:

显而易见的问题是“你为什么要读取整个文件?”如果你的文件变得非常大,这显然不是一个可扩展的解决方案。 scala.io.Source 会从getLines 方法中返回一个Iterator[String],它非常有用且简洁。

使用底层 java IO 实用程序将FileReaderInputStream 转换为String 来进行隐式转换并不是一件容易的事。我认为缺乏可扩展性意味着他们不将其添加到标准 API 是正确的。

【讨论】:

  • 认真的吗?您真正定期阅读的文件中有多少在内存中存在实际问题?我处理过的绝大多数程序中的绝大多数文件都很容易小到可以放入内存中。坦率地说,大数据文件是个例外,如果你要读/写它们,你应该意识到这一点并相应地编程。
  • oxbow_lakes,我不同意。有很多情况涉及到以后大小不会增长的小文件。
  • 我同意它们是例外——但我认为这就是为什么 JDK 或 Scala SDK 中都没有 read-entire-file-into-memory 的原因。这是您自己编写的 3 行实用程序方法:克服它
【解决方案4】:

只是为了扩展 Daniel 的解决方案,您可以通过将以下导入插入到任何需要文件操作的文件中来大大缩短时间:

import scala.io.Source._

有了这个,你现在可以做:

val lines = fromFile("file.txt").getLines

我会警惕将整个文件读入单个String。这是一个非常坏的习惯,它会比你想象的更快更狠地咬你。 getLines 方法返回 Iterator[String] 类型的值。它实际上是文件中的一个惰性光标,允许您只检查所需的数据,而不会冒内存过剩的风险。

哦,回答您关于 Source 的隐含问题:是的,它是规范的 I/O 库。大多数代码最终都使用java.io,因为它具有较低级别的接口和与现有框架更好的兼容性,但任何可以选择的代码都应该使用Source,特别是对于简单的文件操作。

【讨论】:

  • 好的。我对 Source 的负面印象有一个故事:我曾经处于与现在不同的情况,当时我有一个非常大的文件,无法放入内存。使用 Source 导致程序崩溃;事实证明它试图一次阅读整个内容。
  • Source 不应该将整个文件读入内存。如果您在 getLines 之后使用 toList 或其他会生成集合的方法,那么您会将所有内容都放入内存中。现在,Source 是一个hack,旨在完成工作,而不是一个经过深思熟虑的库。它会在 Scala 2.8 中得到改进,但 Scala 社区肯定有机会积极地定义一个好的 I/O API。
【解决方案5】:

有人告诉我 Source.fromFile 有问题。就个人而言,我在使用 Source.fromFile 打开大文件时遇到了问题,不得不求助于 Java InputStreams。

另一个有趣的解决方案是使用 scalax。下面是一些注释良好的代码示例,这些代码使用 ManagedResource 打开日志文件以使用 scalax 帮助程序打开文件:http://pastie.org/pastes/420714

【讨论】:

    【解决方案6】:

    (编辑:这在 scala 2.9 中不起作用,也可能在 2.8 中也不起作用)

    使用主干:

    scala> io.File("/etc/passwd").slurp
    res0: String = 
    ##
    # User Database
    # 
    ... etc
    

    【讨论】:

    • "slurp"?我们真的放弃了明显、直观的名称吗? slurp 的问题在于,至少对于以英语为第一语言的人来说,事后可能是有意义的,但你一开始就不会想到它!
    • 刚刚偶然发现了这个问题/答案。 File 不再在 2.8.0 中,不是吗?
    • 啜饮听起来很棒。 :) 我没想到,但我也不希望屏幕上的输出被命名为“打印”。 slurp 太棒了! :) 太棒了?我没找到。 ;(
    • 在 scala-2.10.0 中,包名是 scala.reflect.io.File 关于这个“文件”的问题。临时,为什么这个文件被标记为“实验”?安全吗?它会释放文件系统的锁吗?
    • slurp 有很长的历史,我认为它起源于 perl
    【解决方案7】:

    正如一些人提到的scala.io.Source,由于连接泄漏,最好避免使用。

    在新的孵化器项目(即 scala-io)被合并之前,可能 scalax 和纯 java 库(如 commons-io)是最佳选择。

    【讨论】:

      【解决方案8】:

      在 scala.io.Source 上使用 getLines() 会丢弃用于行终止符的字符(\n、\r、\r\n 等)

      以下内容应逐个字符地保留它,并且不会进行过多的字符串连接(性能问题):

      def fileToString(file: File, encoding: String) = {
        val inStream = new FileInputStream(file)
        val outStream = new ByteArrayOutputStream
        try {
          var reading = true
          while ( reading ) {
            inStream.read() match {
              case -1 => reading = false
              case c => outStream.write(c)
            }
          }
          outStream.flush()
        }
        finally {
          inStream.close()
        }
        new String(outStream.toByteArray(), encoding)
      }
      

      【讨论】:

        【解决方案9】:

        打印每一行,比如使用 Java BufferedReader 读取每一行,然后打印:

        scala.io.Source.fromFile("test.txt" ).foreach{  print  }
        

        等效:

        scala.io.Source.fromFile("test.txt" ).foreach( x => print(x))
        

        【讨论】:

          【解决方案10】:

          您还可以使用 scala io 中的 Path 来读取和处理文件。

          import scalax.file.Path
          

          现在您可以使用以下方法获取文件路径:-

          val filePath = Path("path_of_file_to_b_read", '/')
          val lines = file.lines(includeTerminator = true)
          

          您也可以包含终止符,但默认设置为 false..

          【讨论】:

            【解决方案11】:

            为了更快地整体读取/上传(大)文件,请考虑增加bufferSize 的大小(Source.DefaultBufSize 设置为2048),例如如下,

            val file = new java.io.File("myFilename")
            io.Source.fromFile(file, bufferSize = Source.DefaultBufSize * 2)
            

            注意Source.scala。如需进一步讨论,请参阅Scala fast text file read and upload to memory

            【讨论】:

              【解决方案12】:

              就像在 Java 中一样,使用 CommonsIO 库:

              FileUtils.readFileToString(file, StandardCharsets.UTF_8)
              

              此外,这里的许多答案都忘记了字符集。最好总是明确地提供它,否则它有一天会发生。

              【讨论】:

                【解决方案13】:

                为了模拟打开和读取文件的 Ruby 语法(并传达语义),请考虑这个隐式类(Scala 2.10 及更高版本),

                import java.io.File
                
                def open(filename: String) = new File(filename)
                
                implicit class RichFile(val file: File) extends AnyVal {
                  def read = io.Source.fromFile(file).getLines.mkString("\n")
                }
                

                这样,

                open("file.txt").read
                

                【讨论】:

                  【解决方案14】:

                  还有一个:https://github.com/pathikrit/better-files#streams-and-codecs

                  在不将内容加载到内存的情况下 slurp 文件的各种方法:

                  val bytes  : Iterator[Byte]            = file.bytes
                  val chars  : Iterator[Char]            = file.chars
                  val lines  : Iterator[String]          = file.lines
                  val source : scala.io.BufferedSource   = file.content 
                  

                  您也可以为任何读/写操作提供自己的编解码器(如果您不提供,则假定为 scala.io.Codec.default):

                  val content: String = file.contentAsString  // default codec
                  // custom codec:
                  import scala.io.Codec
                  file.contentAsString(Codec.ISO8859)
                  //or
                  import scala.io.Codec.string2codec
                  file.write("hello world")(codec = "US-ASCII")
                  

                  【讨论】:

                    【解决方案15】:

                    您不需要解析每一行然后再次连接它们......

                    Source.fromFile(path)(Codec.UTF8).mkString
                    

                    我更喜欢用这个:

                    import scala.io.{BufferedSource, Codec, Source}
                    import scala.util.Try
                    
                    def readFileUtf8(path: String): Try[String] = Try {
                      val source: BufferedSource = Source.fromFile(path)(Codec.UTF8)
                      val content = source.mkString
                      source.close()
                      content
                    }
                    

                    【讨论】:

                    • 你应该关闭流——如果val content = source.mkString出现错误
                    • +1 表示Codec。我在sbt test 上测试失败,因为无法设置它,而 Intellij 的测试命令通过了所有测试。你可以使用def using 来自this
                    【解决方案16】:

                    Java 8+

                    import java.nio.charset.StandardCharsets
                    import java.nio.file.{Files, Paths}
                    
                    val path = Paths.get("file.txt")
                    new String(Files.readAllBytes(path), StandardCharsets.UTF_8)
                    

                    Java 11+

                    import java.nio.charset.StandardCharsets
                    import java.nio.file.{Files, Path}
                    
                    val path = Path.of("file.txt")
                    Files.readString(path, StandardCharsets.UTF_8)
                    

                    这些提供对字符编码的控制,并且无需清理资源。由于更有效的分配模式,它也比其他模式(例如getLines().mkString("\n"))更快。

                    【讨论】:

                    • 为了简化,可以使用Files.readString​(Path.of("file.txt"), StandardCharsets.UTF_8)
                    • 这个答案应该排名更高,因为它是实际关闭文件的答案中最短的答案。当然,它只使用 Java API,但在这种情况下没问题。 (没有暴露可变性)
                    • @Ava,谢谢你的建议。我已经更新了最新的 Java 版本。
                    • 这是最好的答案:+1:
                    【解决方案17】:
                    import scala.io.source
                    object ReadLine{
                    def main(args:Array[String]){
                    if (args.length>0){
                    for (line <- Source.fromLine(args(0)).getLine())
                    println(line)
                    }
                    }
                    

                    在参数中你可以给出文件路径,它将返回所有行

                    【讨论】:

                    • 这个提供了其他答案没有的什么?
                    • 还没有看到其他答案...只是想我可以在这里做出贡献所以发布...希望这不会伤害任何人:)
                    • 你真的应该阅读它们。大多数信息量很大。即使是 8 岁的也有相关信息。
                    【解决方案18】:

                    如果你不介意第三方依赖,你应该考虑使用我的OS-Lib library。这使得读/写文件和使用文件系统非常方便:

                    // Make sure working directory exists and is empty
                    val wd = os.pwd/"out"/"splash"
                    os.remove.all(wd)
                    os.makeDir.all(wd)
                    
                    // Read/write files
                    os.write(wd/"file.txt", "hello")
                    os.read(wd/"file.txt") ==> "hello"
                    
                    // Perform filesystem operations
                    os.copy(wd/"file.txt", wd/"copied.txt")
                    os.list(wd) ==> Seq(wd/"copied.txt", wd/"file.txt")
                    

                    reading bytesreading chunksreading lines 和许多其他有用/常见操作提供单行帮助程序

                    【讨论】:

                      【解决方案19】:

                      你可以使用

                      Source.fromFile(fileName).getLines().mkString
                      

                      但是应该注意 getLines() 会删除所有换行符。 如果你想保存格式,你应该使用

                      Source.fromFile(fileName).iter.mkString
                      

                      【讨论】:

                      • 这个答案没有带来任何新的帮助,已经有很多答案和 cmets 说了同样的话。除非您可以为此添加更多上下文。请阅读:How to give a good answer?
                      猜你喜欢
                      • 2012-05-10
                      • 2021-02-03
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-07-04
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多