如何在Scala中写入文件?

为了阅读,有一个有用的抽象Source 。 我如何写文本文件的行?

编辑(2011年9月):自从Eduardo Costa问起Scala2.9,自从Rick-777评论说scalax.IO的提交历史从2009年年中以来几乎不存在…

Scala-IO已经改变了地方:从Jesse Eichar (也在SO上 )看到它的GitHub 仓库 :

Scala IO伞式项目由IO的不同方面和扩展的几个子项目组成。
Scala IO有两个主要组件:

  • 核心 – 核心主要处理从任意源和汇的数据读写。 angular落的石头特征是InputOutputSeekable ,它们提供了核心API。
    其他类的重要性是ResourceReadCharsWriteChars
  • 文件 – 文件是基于Java 7 NIO文件系统和SBTpath查找器API组合的File (称为Path )API。
    PathFileSystem是Scala IO File 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-incubator / scala-io库。
(如“ 为什么不Scala Sourceclosures底层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中缺less的function之一,我发现它非常有用,我将它添加到我的个人图书馆。 (你也许应该有一个私人图书馆。)代码如下所示:

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

类似于雷克斯克尔的答案,但更通用。 首先我使用一个辅助函数:

 /** * 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() } 

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

这是最简洁和简单的答案 (类似加勒特馆)

 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不需要大括号,也不需要lambdaexpression式,并且注意占位符语法的用法。 还要注意更好的命名。

一个用于保存/读取/从String读取的内容,使用java.nio

 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 } 

这不适合大文件,但会做这项工作。

一些链接:

java.nio.file.Files.write
java.lang.String.getBytes
scala.collection.JavaConverters
scala.collection.immutable.List.mkString

下面是一个使用Scala编译器库的简明单线程:

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

另外,如果你想使用Java库,你可以做这个黑客:

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

从scala将string写入一个语句中的文件

我写的一个微库: https : //github.com/pathikrit/better-files

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

要么

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

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

  1. 在Jus12的答案中 ,对Scala / FP初学者来说,使用辅助方法的currying是不明显的
  2. 需要用scala.util.Try封装较低级别的错误
  3. 需要向Scala / FP展示新的Java开发人员如何正确地嵌套依赖资源,以便以相反的顺序在每个依赖资源上执行close方法 – 注意:以相反的顺序closures相关资源特别是在失败的情况下是一个很less理解的需求的java.lang.AutoCloseable规范,这往往会导致非常有害的,很难find错误和运行时间故障

在开始之前,我的目标不是简洁。 这是为了便于理解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 => try transfer(autoCloseable) finally autoCloseable.close() ) 

上面的tryUsingAutoCloseable方法的开始可能会令人困惑,因为它似乎有两个参数列表,而不是习惯的单个参数列表。 这就是所谓的咖喱。 我不会详细介绍如何进行curl作品或偶尔有用的地方。 事实certificate,对于这个特定的问题空间,这是工作的正确工具。

接下来,我们需要创buildtryPrintToFile方法,它将创build一个(或覆盖现有的) File并写入一个List[String] 。 它使用一个由BufferedWriter封装的FileWriter ,它由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]作为input并将其发送到File 。 现在让我们创build一个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)) } } } 

最后,能够将File的内容作为String获取是有用的。 虽然scala.io.Source提供了一种轻松获取File内容的方便方法,但是必须在Source上使用close方法来释放底层的JVM和文件系统句柄。 如果没有这样做,那么在JVM GC(垃圾收集器)开始释放Source实例本身之前,资源不会被释放。 即使这样,只有一个弱的JVM保证, finalize方法将被GC调用来close资源。 这意味着显式调用close方法是客户端的责任,就像客户端负责close一个java.lang.AutoCloseable实例一样。 为此,我们需要处理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 => try transfer(source)) finally source.close() ) 

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

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

已经提供了上述函数的更新版本,作为不同但相关的StackOverflow问题的答案。


现在,将所有这些与提取的input结合在一起(使得更易于粘贴到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)(transfer: A => scala.util.Try[R]): scala.util.Try[R] = Try(instantiateAutoCloseable()) .flatMap( autoCloseable => try transfer(autoCloseable)) finally autoCloseable.close() ) def tryUsingSource[S <: scala.io.Source, R] (instantiateSource: () => S)(transfer: S => scala.util.Try[R]): scala.util.Try[R] = Try(instantiateSource()) .flatMap( source => try transfer(source)) finally source.close() ) 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新手,我已经烧了很多小时(大部分都是头痛的挫折感),获得了上述的知识和解决scheme。 我希望这有助于其他Scala / FP新手克服这个特殊的学习驼峰更快。

下面是使用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 

为了超越他和他之前的贡献者,我改进了命名和简洁:

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

没有依赖,有error handling

  • 仅使用标准库中的方法
  • 如有必要,创build该文件的目录
  • 用于Eithererror handling

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

这一行有助于从一个数组或string中写入一个文件。

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