如何在 Scala 中写入文件?

对于阅读,有一个非常有用的抽象 Source。我如何将行写入文本文件?

161088 次浏览

编辑2019年(8年后) ,Scala-IO不是很活跃,如果有,李好一建议他自己的库 lihaoyi/os-lib,他 下文将介绍

2019年6月,Xavier Guihot他的回答中提到了库 强 > Using,一个用于执行自动资源管理的实用程序。


编辑(2011年9月) : 由于 爱德华多 · 科斯塔询问 Scala2.9,而且由于 Rick-777评论说 Scalax.IO 提交历史记录自2009年中期以来几乎不存在..。

Scala-IO 改变了位置: 参见它的 GitHub 回收站,来自 Jesse Eichar(也是 所以) :

Scala IO 保护伞项目由一些针对 IO 的不同方面和扩展的子项目组成。
Scala IO 有两个主要组件:

  • Core -Core 主要处理从任意源和汇读取和写入数据。基石性状为 InputOutputSeekable,它们提供了核心 API。
    其他重要的类别是 ResourceReadCharsWriteChars
  • File -File 是一个 File(称为 Path) API,它基于 Java 7 NIO 文件系统和 SBT PathFinder API 的组合。
    PathFileSystem是 Scala IO 文件 API 的主要入口点。
import scalax.io._


val output:Output = Resource.fromFile("someFile")


// Note: each write will open a new connection to file and
//       each write is executed at the begining of the file,
//       so in this case the last write will be the contents of the file.
// See Seekable for append and patching files
// Also See openOutput for performing several writes with a single connection


output.writeIntsAsBytes(1,2,3)
output.write("hello")(Codec.UTF8)
output.writeStrings(List("hello","world")," ")(Codec.UTF8)

原始答案(2011年1月) ,用老地方 Scala-io:

如果不想等待 Scala2.9,可以使用 Scala-孵化器/scala-io库。
(如「 为什么 Scala Source 不关闭底层的 InputStream?」所述)

参见 样本

{ // several examples of writing data
import scalax.io.{
FileOps, Path, Codec, OpenOption}
// the codec must be defined either as a parameter of ops methods or as an implicit
implicit val codec = scalax.io.Codec.UTF8




val file: FileOps = Path ("file")


// write bytes
// By default the file write will replace
// an existing file with the new data
file.write (Array (1,2,3) map ( _.toByte))


// another option for write is openOptions which allows the caller
// to specify in detail how the write should take place
// the openOptions parameter takes a collections of OpenOptions objects
// which are filesystem specific in general but the standard options
// are defined in the OpenOption object
// in addition to the definition common collections are also defined
// WriteAppend for example is a List(Create, Append, Write)
file.write (List (1,2,3) map (_.toByte))


// write a string to the file
file.write("Hello my dear file")


// with all options (these are the default options explicitely declared)
file.write("Hello my dear file")(codec = Codec.UTF8)


// Convert several strings to the file
// same options apply as for write
file.writeStrings( "It costs" :: "one" :: "dollar" :: Nil)


// Now all options
file.writeStrings("It costs" :: "one" :: "dollar" :: Nil,
separator="||\n||")(codec = Codec.UTF8)
}

这是标准 Scala 缺少的特性之一,我发现它非常有用,所以我把它添加到我的个人库中。(你可能也应该有一个个人图书馆。)代码是这样的:

def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) {
val p = new java.io.PrintWriter(f)
try { op(p) } finally { p.close() }
}

它是这样使用的:

import java.io._
val data = Array("Five","strings","in","a","file!")
printToFile(new File("example.txt")) { p =>
data.foreach(p.println)
}

与 Rex Kerr 的答案相似,但更通用:

/**
* Used for reading/writing to database, files, etc.
* Code From the book "Beginning Scala"
* http://www.amazon.com/Beginning-Scala-David-Pollak/dp/1430219890
*/
def using[A <: {def close(): Unit}, B](param: A)(f: A => B): B =
try { f(param) } finally { param.close() }

然后我用这个作为:

def writeToFile(fileName:String, data:String) =
using (new FileWriter(fileName)) {
fileWriter => fileWriter.write(data)
}

还有

def appendToFile(fileName:String, textData:String) =
using (new FileWriter(fileName, true)){
fileWriter => using (new PrintWriter(fileWriter)) {
printWriter => printWriter.println(textData)
}
}

等等。

一个简单的答案:

import java.io.File
import java.io.PrintWriter


def writeToFile(p: String, s: String): Unit = {
val pw = new PrintWriter(new File(p))
try pw.write(s) finally pw.close()
}

下面是一个使用 Scala 编译器库的简洁的一行程序:

scala.tools.nsc.io.File("filename").writeAll("hello world")

或者,如果你想使用 Java 库,你可以这样做:

Some(new PrintWriter("filename")).foreach{p => p.write("hello world"); p.close}

给出另一个答案,因为我编辑的其他答案在哪里被拒绝了。

这是 最简明扼要的回答(类似于加勒特霍尔的)

File("filename").writeAll("hello world")

这与 Jus12类似,但是没有冗长,而且使用的是正确的 代码风格

def using[A <: {def close(): Unit}, B](resource: A)(f: A => B): B =
try f(resource) finally resource.close()


def writeToFile(path: String, data: String): Unit =
using(new FileWriter(path))(_.write(data))


def appendToFile(path: String, data: String): Unit =
using(new PrintWriter(new FileWriter(path, true)))(_.println(data))

注意,try finally和 lambdas 都不需要大括号,请注意占位符语法的用法。还要注意更好的命名。

使用 java.nio保存/读取到/从 String的一行代码。

import java.nio.file.{Paths, Files, StandardOpenOption}
import java.nio.charset.{StandardCharsets}
import scala.collection.JavaConverters._


def write(filePath:String, contents:String) = {
Files.write(Paths.get(filePath), contents.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE)
}


def read(filePath:String):String = {
Files.readAllLines(Paths.get(filePath), StandardCharsets.UTF_8).asScala.mkString
}

这不适合大文件,但可以完成这项工作。

有关连结:

Files.write
Java.lang. String.getBytes
集合,JavaConverters
Immutable. List.mkString

为了超越同样的最好的和在他之前的贡献者,我改进了命名和简洁性:

  def using[A <: {def close() : Unit}, B](resource: A)(f: A => B): B =
try f(resource) finally resource.close()


def writeStringToFile(file: File, data: String, appending: Boolean = false) =
using(new FileWriter(file, appending))(_.write(data))

下面是使用 Scalaz-stream向文件写入一些行的示例。

import scalaz._
import scalaz.stream._


def writeLinesToFile(lines: Seq[String], file: String): Task[Unit] =
Process(lines: _*)              // Process that enumerates the lines
.flatMap(Process(_, "\n"))    // Add a newline after each line
.pipe(text.utf8Encode)        // Encode as UTF-8
.to(io.fileChunkW(fileName))  // Buffered write to the file
.runLog[Task, Unit]           // Get this computation as a Task
.map(_ => ())                 // Discard the result


writeLinesToFile(Seq("one", "two"), "file.txt").run

我写的一个微型图书馆: https://github.com/pathikrit/better-files

file.appendLine("Hello", "World")

或者

file << "Hello" << "\n" << "World"

2019年9月1日最新情况:

  • 从 Scala 2.13开始,更喜欢使用 Scala.util 使用
  • 修正了如果 finally代码抛出 Exceptionfinally将吞噬 try抛出的原始 Exception的错误

在回顾了所有这些关于如何轻松地用 Scala 编写文件的答案之后,其中一些非常不错,我遇到了三个问题:

  1. 12号的答案中,对于 Scala/FP 初学者来说,使用帮助器方法的局部套用是不明显的
  2. 需要用 scala.util.Try封装较低级别的错误
  3. 需要向 Scala/FP 的 Java 开发人员展示如何正确地嵌套 受供养人资源,以便 close方法在每个依赖资源上以相反的顺序执行—— 注:以相反的顺序关闭依赖资源 特别是在失败的情况下java.lang.AutoCloseable规范中一个很少被理解的要求,它往往导致非常有害和难以发现错误和运行时故障

在开始之前,我的目标不是简洁。它是为了方便 Scala/FP 初学者,尤其是那些来自 Java 的初学者,更容易理解。在最后,我会把所有的部分组合在一起,然后增加简洁性。

首先,需要更新 using方法以使用 Try(同样,简洁性不是这里的目标)。它将被重新命名为 tryUsingAutoCloseable:

def tryUsingAutoCloseable[A <: AutoCloseable, R]
(instantiateAutoCloseable: () => A) //parameter list 1
(transfer: A => scala.util.Try[R])  //parameter list 2
: scala.util.Try[R] =
Try(instantiateAutoCloseable())
.flatMap(
autoCloseable => {
var optionExceptionTry: Option[Exception] = None
try
transfer(autoCloseable)
catch {
case exceptionTry: Exception =>
optionExceptionTry = Some(exceptionTry)
throw exceptionTry
}
finally
try
autoCloseable.close()
catch {
case exceptionFinally: Exception =>
optionExceptionTry match {
case Some(exceptionTry) =>
exceptionTry.addSuppressed(exceptionFinally)
case None =>
throw exceptionFinally
}
}
}
)

上面的 tryUsingAutoCloseable方法的开头可能会让人感到困惑,因为它似乎有两个参数列表,而不是通常的单个参数列表。这叫做咖喱。我不会详细介绍咖喱是如何工作的,或者它在哪里有用。事实证明,对于这个特定的问题空间,它是完成工作的正确工具。

接下来,我们需要创建方法 tryPrintToFile,它将创建(或覆盖现有的) File并编写 List[String]。它使用一个 FileWriter,它被一个 BufferedWriter封装,而 BufferedWriter又被一个 PrintWriter封装。为了提高性能,定义了一个比 BufferedWriter的默认缓冲区大得多的缺省缓冲区大小 defaultBufferSize,并分配了值65536。

下面是代码(同样,简洁并不是这里的目标) :

val defaultBufferSize: Int = 65536


def tryPrintToFile(
lines: List[String],
location: java.io.File,
bufferSize: Int = defaultBufferSize
): scala.util.Try[Unit] = {
tryUsingAutoCloseable(() => new java.io.FileWriter(location)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
fileWriter =>
tryUsingAutoCloseable(() => new java.io.BufferedWriter(fileWriter, bufferSize)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
bufferedWriter =>
tryUsingAutoCloseable(() => new java.io.PrintWriter(bufferedWriter)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
printWriter =>
scala.util.Try(
lines.foreach(line => printWriter.println(line))
)
}
}
}
}

上面的 tryPrintToFile方法很有用,因为它接受一个 List[String]作为输入,并将其发送到一个 File。现在让我们创建一个 tryWriteToFile方法,它接受一个 String并将其写入 File

下面是代码(这里我将让您猜测简洁性的优先级) :

def tryWriteToFile(
content: String,
location: java.io.File,
bufferSize: Int = defaultBufferSize
): scala.util.Try[Unit] = {
tryUsingAutoCloseable(() => new java.io.FileWriter(location)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
fileWriter =>
tryUsingAutoCloseable(() => new java.io.BufferedWriter(fileWriter, bufferSize)) { //this open brace is the start of the second curried parameter to the tryUsingAutoCloseable method
bufferedWriter =>
Try(bufferedWriter.write(content))
}
}
}

最后,能够以 String的形式获取 File的内容是非常有用的。虽然 scala.io.Source提供了一种方便的方法来轻松获取 File的内容,但是必须在 Source上使用 close方法来释放底层的 JVM 和文件系统句柄。如果不这样做,那么资源就不会释放,直到 JVM GC (垃圾收集器)开始释放 Source实例本身。即使这样,也只有一个弱 JVM 保证 finalize方法将被 GC 调用到 close资源。这意味着显式调用 close方法是客户机的责任,就像客户机在 String1的实例上调用高 close一样。为此,我们需要处理 scala.io.Source的 using 方法的第二个定义。

下面是解决这个问题的代码(仍然不够简洁) :

def tryUsingSource[S <: scala.io.Source, R]
(instantiateSource: () => S)
(transfer: S => scala.util.Try[R])
: scala.util.Try[R] =
Try(instantiateSource())
.flatMap(
source => {
var optionExceptionTry: Option[Exception] = None
try
transfer(source)
catch {
case exceptionTry: Exception =>
optionExceptionTry = Some(exceptionTry)
throw exceptionTry
}
finally
try
source.close()
catch {
case exceptionFinally: Exception =>
optionExceptionTry match {
case Some(exceptionTry) =>
exceptionTry.addSuppressed(exceptionFinally)
case None =>
throw exceptionFinally
}
}
}
)

下面是它在一个超级简单的行流文件阅读器(目前用于从数据库输出中读取以制表符分隔的文件)中的用法示例:

def tryProcessSource(
file: java.io.File
, parseLine: (String, Int) => List[String] = (line, index) => List(line)
, filterLine: (List[String], Int) => Boolean = (values, index) => true
, retainValues: (List[String], Int) => List[String] = (values, index) => values
, isFirstLineNotHeader: Boolean = false
): scala.util.Try[List[List[String]]] =
tryUsingSource(scala.io.Source.fromFile(file)) {
source =>
scala.util.Try(
( for {
(line, index) <-
source.getLines().buffered.zipWithIndex
values =
parseLine(line, index)
if (index == 0 && !isFirstLineNotHeader) || filterLine(values, index)
retainedValues =
retainValues(values, index)
} yield retainedValues
).toList //must explicitly use toList due to the source.close which will
//occur immediately following execution of this anonymous function
)
)

提供了一个 上述功能的更新版本作为对 不同但相关的堆栈溢出问题的回答。


现在,把所有这些与提取的导入结合起来(使得粘贴到 Eclipse ScalaIDE 和 IntelliJ Scala 插件中的 Scala Worksheet 变得更加容易,从而更容易将输出转储到桌面,以便更容易用文本编辑器检查) ,这就是代码的样子(更加简洁) :

import scala.io.Source
import scala.util.Try
import java.io.{BufferedWriter, FileWriter, File, PrintWriter}


val defaultBufferSize: Int = 65536


def tryUsingAutoCloseable[A <: AutoCloseable, R]
(instantiateAutoCloseable: () => A) //parameter list 1
(transfer: A => scala.util.Try[R])  //parameter list 2
: scala.util.Try[R] =
Try(instantiateAutoCloseable())
.flatMap(
autoCloseable => {
var optionExceptionTry: Option[Exception] = None
try
transfer(autoCloseable)
catch {
case exceptionTry: Exception =>
optionExceptionTry = Some(exceptionTry)
throw exceptionTry
}
finally
try
autoCloseable.close()
catch {
case exceptionFinally: Exception =>
optionExceptionTry match {
case Some(exceptionTry) =>
exceptionTry.addSuppressed(exceptionFinally)
case None =>
throw exceptionFinally
}
}
}
)


def tryUsingSource[S <: scala.io.Source, R]
(instantiateSource: () => S)
(transfer: S => scala.util.Try[R])
: scala.util.Try[R] =
Try(instantiateSource())
.flatMap(
source => {
var optionExceptionTry: Option[Exception] = None
try
transfer(source)
catch {
case exceptionTry: Exception =>
optionExceptionTry = Some(exceptionTry)
throw exceptionTry
}
finally
try
source.close()
catch {
case exceptionFinally: Exception =>
optionExceptionTry match {
case Some(exceptionTry) =>
exceptionTry.addSuppressed(exceptionFinally)
case None =>
throw exceptionFinally
}
}
}
)


def tryPrintToFile(
lines: List[String],
location: File,
bufferSize: Int = defaultBufferSize
): Try[Unit] =
tryUsingAutoCloseable(() => new FileWriter(location)) { fileWriter =>
tryUsingAutoCloseable(() => new BufferedWriter(fileWriter, bufferSize)) { bufferedWriter =>
tryUsingAutoCloseable(() => new PrintWriter(bufferedWriter)) { printWriter =>
Try(lines.foreach(line => printWriter.println(line)))
}
}
}


def tryWriteToFile(
content: String,
location: File,
bufferSize: Int = defaultBufferSize
): Try[Unit] =
tryUsingAutoCloseable(() => new FileWriter(location)) { fileWriter =>
tryUsingAutoCloseable(() => new BufferedWriter(fileWriter, bufferSize)) { bufferedWriter =>
Try(bufferedWriter.write(content))
}
}


def tryProcessSource(
file: File,
parseLine: (String, Int) => List[String] = (line, index) => List(line),
filterLine: (List[String], Int) => Boolean = (values, index) => true,
retainValues: (List[String], Int) => List[String] = (values, index) => values,
isFirstLineNotHeader: Boolean = false
): Try[List[List[String]]] =
tryUsingSource(() => Source.fromFile(file)) { source =>
Try(
( for {
(line, index) <- source.getLines().buffered.zipWithIndex
values = parseLine(line, index)
if (index == 0 && !isFirstLineNotHeader) || filterLine(values, index)
retainedValues = retainValues(values, index)
} yield retainedValues
).toList
)
}

作为一个 Scala/FP 的新手,我已经花费了很多时间(大部分是在令人头痛的挫折中)来获得上述知识和解决方案。我希望这能帮助其他 Scala/FP 新手更快地克服这个特殊的学习难关。

没有依赖关系,有错误处理

  • 仅使用标准库中的方法
  • 如果需要,为文件创建目录
  • 使用 Either进行错误处理

密码

def write(destinationFile: Path, fileContent: String): Either[Exception, Path] =
write(destinationFile, fileContent.getBytes(StandardCharsets.UTF_8))


def write(destinationFile: Path, fileContent: Array[Byte]): Either[Exception, Path] =
try {
Files.createDirectories(destinationFile.getParent)
// Return the path to the destinationFile if the write is successful
Right(Files.write(destinationFile, fileContent))
} catch {
case exception: Exception => Left(exception)
}

用法

val filePath = Paths.get("./testDir/file.txt")


write(filePath , "A test") match {
case Right(pathToWrittenFile) => println(s"Successfully wrote to $pathToWrittenFile")
case Left(exception) => println(s"Could not write to $filePath. Exception: $exception")
}

此行有助于从 Array 或 String 编写文件。

 new PrintWriter(outputPath) { write(ArrayName.mkString("")); close }

如果你的项目中有 Akka Streams,它提供了一个简单的例子:

def writeToFile(p: Path, s: String)(implicit mat: Materializer): Unit = {
Source.single(ByteString(s)).runWith(FileIO.toPath(p))
}

Akka 文档 > 流文件 IO

2019年更新:

摘要-JavaNIO (或异步的 NIO.2)仍然是 Scala 支持的最全面的文件处理解决方案。下面的代码创建一些文本并将其写入新文件:

import java.io.{BufferedOutputStream, OutputStream}
import java.nio.file.{Files, Paths}


val testFile1 = Paths.get("yourNewFile.txt")
val s1 = "text to insert in file".getBytes()


val out1: OutputStream = new BufferedOutputStream(
Files.newOutputStream(testFile1))


try {
out1.write(s1, 0, s1.length)
} catch {
case _ => println("Exception thrown during file writing")
} finally {
out1.close()
}
  1. 导入 Java 库: IO 和 NIO
  2. 使用您选择的文件名创建一个 Path对象
  3. 将要插入到文件中的文本转换为字节数组
  4. 以流的形式获取文件: OutputStream
  5. 将字节数组传递到输出流的 write函数中
  6. 关闭小溪

Scala 2.13开始,标准库提供了一个专用的资源管理实用程序: Using

在这种情况下,它可以与扩展了 AutoCloseablePrintWriterBufferedWriter等资源一起使用,以便写入一个文件,然后无论如何关闭资源:

  • 例如,使用 java.io api:

    import scala.util.Using, java.io.{PrintWriter, File}
    
    
    // val lines = List("hello", "world")
    Using(new PrintWriter(new File("file.txt"))) {
    writer => lines.foreach(writer.println)
    }
    
  • Or with java.nio api:

    import scala.util.Using, java.nio.file.{Files, Paths}, java.nio.charset.Charset
    
    
    // val lines = List("hello", "world")
    Using(Files.newBufferedWriter(Paths.get("file.txt"), Charset.forName("UTF-8"))) {
    writer => lines.foreach(line => writer.write(line + "\n"))
    }
    

不幸的是,对于顶级答案来说,Scala-IO 已经死了。如果您不介意使用第三方依赖项,可以考虑使用我的 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")

它具有用于 写入文件附加到文件中覆盖文件和许多其他有用/常用操作的一行程序

这个答案类似,下面是 fs2(版本1.0.4)的一个示例:

import cats.effect._


import fs2._
import fs2.io


import java.nio.file._


import scala.concurrent.ExecutionContext
import scala.language.higherKinds
import cats.syntax.functor._


object ScalaApp extends IOApp {


def write[T[_]](p: Path, s: String)
(implicit F: ConcurrentEffect[T], cs: ContextShift[T]): T[Unit] = {
Stream(s)
.covary[T]
.through(text.utf8Encode)
.through(
io.file.writeAll(
p,
scala.concurrent.ExecutionContext.global,
Seq(StandardOpenOption.CREATE)
)
)
.compile
.drain
}




def run(args: List[String]): IO[ExitCode] = {


implicit val executionContext: ExecutionContext =
scala.concurrent.ExecutionContext.Implicits.global


implicit val contextShift: ContextShift[IO] =
IO.contextShift(executionContext)


val outputFile: Path = Paths.get("output.txt")


write[IO](outputFile, "Hello world\n").as(ExitCode.Success)


}
}
import java.io.File
import java.io.PrintWriter


val file = new File("tmp/cloudwatch/metric.json")
val writer = new PrintWriter(file);
writer.write(stream);
writer.close();

对我有用