在Scala中读取整个文件?


312

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

我能想到的最好的是:

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

还是我应该使用Java令人敬畏的惯用法之一,最好的用法是(不使用外部库):

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()

12
如果您知道正确的工具,那么Java并不是那么糟糕。导入org.apache.commons.io.FileUtils; FileUtils.readFileToString(new File(“ file.txt”,“ UTF-8”)
smartnut007 2011年

25
这个评论错过了语言设计的重点。因此,任何可以为您要执行的操作提供简单库函数的语言,都与其函数调用语法一样好。给定一个无限且100%记忆的库,所有程序将通过单个函数调用来实现。当编程语言需要较少的预制组件以实现特定结果时,它是一种很好的编程语言。
克里斯·芒特福德

Answers:


429
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"

48
我参加聚会太晚了,但我讨厌人们不知道他们可以在树干中执行“ io.File(“ / etc / passwd”)。slurp“”。
psp

28
@extempore如果您真的认为我很感恩,我真的很抱歉。我非常感谢您对Scala语言的支持,并且每次您亲自调查我提出的问题,提出解决我所遇到问题的方法或向我解释某些内容时,我都会深表感谢。然后,我将借此机会感谢您将scala.io变成了不错的东西。从现在开始,我会更加表达自己的谢意,但抱歉,我仍然讨厌这个名字。
Daniel C. Sobral

49
多年来,“ slurp”一直是一次在Perl中一次读取整个文件的名称。Perl比C语言家族更具内在和非正式的命名传统,有些人可能会觉得讨厌,但在这种情况下,我认为这很合适:对于丑陋的实践来说,这是一个丑陋的词。当您使用slurp()时,您知道自己在做一些顽皮的事情,因为您只需要键入该内容即可。
Marcus Downing

15
File.read()将是一个更好的名称,并且与Ruby和Python一致。
布伦丹·奥康纳

26
@extempore:您不能阻止人们感到反感。就是这样。有些人不喜欢您所做的每一个选择,这不应该让您感到困扰。那就是生活,不能让所有人
满意

58

只是为了扩展Daniel的解决方案,您可以通过将以下导入插入到任何需要文件操作的文件中来极大地简化事情:

import scala.io.Source._

这样,您现在可以执行以下操作:

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

我会警惕将整个文件读入一个文件中String。这是一个非常不好的习惯,一个习惯会比您想像的要早,更难地咬您。该getLines方法返回type的值Iterator[String]。它实际上是文件中的惰性游标,使您可以检查所需的数据而不会冒内存过多的风险。

哦,并且回答您暗含的问题Source:是的,它是规范的I / O库。大多数代码java.io由于其较低级别的界面以及与现有框架的更好兼容性而最终使用,但是任何可以选择的代码都应使用Source,尤其是对于简单的文件操作。


好。有一个故事让我对Source产生了负面印象:我曾经处于与现在不同的境地,当时我有一个非常大的文件,无法容纳到内存中。使用Source导致程序崩溃;事实证明,它试图一次读取整个内容。
布伦丹·奥康纳

7
源不应该将整个文件读入内存。如果在getLines之后使用toList或其他将产生集合的方法,则将所有内容存储到内存中。现在,Source是一个hack,旨在完成工作,而不是经过深思熟虑的库。它会在Scala 2.8中得到改进,但是Scala社区肯定有机会活跃于定义良好的I / O API。
Daniel C. Sobral

36
// for file with utf-8 encoding
val lines = scala.io.Source.fromFile("file.txt", "utf-8").getLines.mkString

6
在原始答案中添加“ getLines”将删除所有换行符。应该是“ Source.fromFile(“ file.txt”,“ utf-8”)。mkString”。

9
另请参阅我在Daniel C. Sobral的回答中的评论-这种用法不会关闭Source实例,因此Scala可能会在文件上保留锁定。
djb

26

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

使用中继线:

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

14
slurp“?我们是否真的放弃了显而易见的直观名称?问题slurp在于,至少对于以英语为第一语言的人来说,事后才有意义,但您永远不会想到它!
Daniel C. Sobral

5
刚发现这个问题/答案。File在2.8.0中不再存在,不是吗?
huynhjl

4
喝听起来很棒。:)我没想到,但是我也没想到输出到屏幕上的东西也被称为“打印”。slurp太棒了!:)太棒了吗?我没找到 ;(
用户未知

5
在scala-2.10.0中,程序包名称为scala.reflect.io.File,有关此“文件”的问题。临时,为什么将此文件标记为“实验性”?安全吗?它会释放对文件系统的锁定吗?
VasiliNovikov 2013年

4
slurp为此目的拥有悠久的历史,我认为是perl
Chris Mountford,2015年

18
import java.nio.charset.StandardCharsets._
import java.nio.file.{Files, Paths}

new String(Files.readAllBytes(Paths.get("file.txt")), UTF_8)

控制字符编码,没有资源可清理。此外,可能会进行优化(例如,Files.readAllBytes分配适合文件大小的字节数组)。


7

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

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


6

在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)
}

6

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

在不将内容加载到内存的情况下处理文件的多种方法:

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")

5

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

FileUtils.readFileToString(file, StandardCharsets.UTF_8)

另外,这里的许多答案都忘记了Charset。最好始终明确提供它,否则它将一天之内。


4

为了模拟打开和读取文件的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

3

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

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


3

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

import scalax.file.Path

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

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

您还可以包括终结符,但默认情况下将其设置为false。


3

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

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

注意Source.scala。有关进一步的讨论,请参见Scala快速文本文件读取并上传到内存


3

您无需解析每一行,然后再次将它们串联起来...

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
Andrzej Jozwik '18

为+1 Codec。我无法通过测试设置失败sbt test,而Intellij的测试命令则通过了所有测试。您还可以使用def using
米哈伊尔Ionkin

3

如果您不介意第三方依赖性,则应考虑使用OS-Lib库。这使得读取/写入文件和使用文件系统非常方便:

// 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")

使用单行助手来读取字节读取块读取行以及许多其他有用/通用的操作


2

显而易见的问题是“为什么要读取整个文件?” 如果文件很大,这显然不是可伸缩的解决方案。该scala.io.Source给你回一个Iterator[String]getLines方法,这是非常有用的,简洁。

使用底层java IO实用程序将a File,a Reader或an 转换为a进行隐式转换并不是一件InputStream容易的事String。我认为缺乏可伸缩性意味着他们正确的做法是不将其添加到标准API中。


12
认真吗 您真正定期阅读了多少文件,这些文件在内存中确实有实际问题?我曾经处理过的绝大多数程序中的绝大多数文件都很小,足以装入内存。坦白说,大数据文件是个例外,如果您要读取/写入它们,则应意识到这一点并进行相应编程。
Christopher

8
oxbow_lakes,我不同意。在许多情况下,涉及小的文件的大小将来将不会增长。
布伦丹·奥康纳

4
我同意它们是例外-但是我认为这就是为什么在JDK或Scala SDK中都没有将整个文件读入内存的原因。这是您编写自己的3行实用程序方法:克服它
oxbow_lakes 2009年

1

打印每一行,就像使用Java BufferedReader读取每行一样,并打印它:

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

当量:

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

0
import scala.io.source
object ReadLine{
def main(args:Array[String]){
if (args.length>0){
for (line <- Source.fromLine(args(0)).getLine())
println(line)
}
}

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


3
另一个答案没有提供什么呢?
jwvh

还没有看到其他答案...只是想我可以在这里提供帮助,所以希望...不会对任何人造成伤害:)
Apurw

1
您确实应该阅读它们。大多数都提供了很多信息。即使是8岁的孩子也有相关信息。
jwvh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.