对于阅读,有一个有用的抽象 Source
。如何将行写入文本文件?
这是标准 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)
}
编辑 2019 年(8 年后),Scala-IO 不是很活跃,如果有的话,Li Haoyi 建议他自己的图书馆 lihaoyi/os-lib
,他presents below。
2019 年 6 月,Xavier Guihot 在 his answer 库 Using
中提到了一个用于执行自动资源管理的实用程序。
编辑(2011 年 9 月):因为 Eduardo Costa 询问 Scala2.9,并且因为 Rick-777 评论说 scalax.IO commit history 自 2009 年中期以来几乎不存在......
Scala-IO 已更改位置:查看其 GitHub repo,来自 Jesse Eichar(也称为 on SO):
Scala IO 伞形项目由几个子项目组成,用于 IO 的不同方面和扩展。 Scala IO 有两个主要组件: 核心 - 核心主要处理从任意源和接收器读取和写入数据。基石特征是提供核心 API 的 Input、Output 和 Seekable。其他重要的类别是 Resource、ReadChars 和 WriteChars。 File - File 是一个 File(称为 Path)API,它基于 Java 7 NIO 文件系统和 SBT PathFinder API 的组合。 Path 和 FileSystem 是 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 库。
(如“Why doesn't Scala Source close the underlying InputStream?”中所述)
请参阅the samples
{ // 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)
}
类似于 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()
}
import
的库吗?
给出另一个答案,因为我对其他答案的编辑被拒绝了。
这是最简洁的答案(类似于 Garret Hall 的)
File("filename").writeAll("hello world")
这类似于 Jus12,但没有冗长和正确的 code style
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
的花括号,也不需要 lambda,并注意占位符语法的用法。还要注意更好的命名。
implemented
先决条件。您不能使用未实现的代码。我的意思是你必须告诉如何找到它,因为它在默认情况下不可用并且不为人所知。
不幸的是,对于最佳答案,Scala-IO 已经死了。如果您不介意使用第三方依赖项,请考虑使用我的 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")
它具有用于 writing to files、appending to files、overwriting files 和许多其他有用/常见操作的单行代码
这是使用 Scala 编译器库的简明单行代码:
scala.tools.nsc.io.File("filename").writeAll("hello world")
或者,如果你想使用 Java 库,你可以这样做:
Some(new PrintWriter("filename")).foreach{p => p.write("hello world"); p.close}
scala.tools.nsc.io.File("/tmp/myFile.txt")
适用于 Scala 2.11.8。
使用 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
}
这不适合大文件,但可以完成这项工作。
一些链接:
java.nio.file.Files.write
java.lang.String.getBytes
scala.collection.JavaConverters
scala.collection.immutable.List.mkString
write
会将 contents
复制到一个新的字节数组而不是将其流式传输到文件中,因此在其峰值使用的内存是单独使用 contents
的两倍。
从 Scala 2.13
开始,标准库提供了一个专用的资源管理实用程序:Using
。
在这种情况下,它可以与扩展 AutoCloseable
的 PrintWriter
或 BufferedWriter
等资源一起使用,以便写入文件,无论如何,之后关闭资源:
例如,使用 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) }
或者使用 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")) }
我写的一个微库:https://github.com/pathikrit/better-files
file.appendLine("Hello", "World")
或者
file << "Hello" << "\n" << "World"
2019/Sep/01 更新:
从 Scala 2.13 开始,更喜欢使用 scala.util.Using
修复了如果 finally 代码抛出异常,finally 会吞下 try 抛出的原始异常的错误
在查看了所有这些关于如何在 Scala 中轻松编写文件的答案(其中一些非常好)之后,我遇到了三个问题:
在 Jus12 的回答中,对于 Scala/FP 初学者来说,使用 helper 方法使用柯里化并不明显需要用 scala.util.Try 封装较低级别的错误。需要向刚接触 Scala/FP 的 Java 开发人员展示如何正确嵌套依赖资源,因此关闭方法以相反的顺序对每个依赖资源执行 - 注意:以相反的顺序关闭依赖资源,特别是在失败的情况下是 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
方法的开头可能会令人困惑,因为它似乎有两个参数列表,而不是通常的单个参数列表。这称为柯里化。我不会详细介绍 currying 的工作原理或它偶尔有用的地方。事实证明,对于这个特定的问题空间,它是适合这项工作的工具。
接下来,我们需要创建方法 tryPrintToFile
,它将创建(或覆盖现有的)File
并写入 List[String]
。它使用由 BufferedWriter
封装的 FileWriter
,而 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))
}
}
}
最后,能够将 File
的内容作为 String
获取是很有用的。虽然 scala.io.Source
提供了一种方便的方法来轻松获取 File
的内容,但必须在 Source
上使用 close
方法来释放底层 JVM 和文件系统句柄。如果不这样做,则在 JVM GC(垃圾收集器)开始释放 Source
实例本身之前,不会释放资源。即使这样,也只有一个弱 JVM 保证 GC 将调用 finalize
方法来 close
资源。这意味着显式调用 close
方法是客户的责任,就像客户在 java.lang.AutoCloseable
的实例上提高 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
)
)
已提供 updated version of the above function 作为对 different but related StackOverflow question 的回答。
现在,将所有这些与提取的导入结合在一起(使其更容易粘贴到 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 新手更快地克服这个特殊的学习障碍。
try-catch-finally
替换。还是爱你的热情。
下面是使用 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
为了超越 samthebest 和他之前的贡献者,我改进了命名和简洁性:
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))
无依赖,有错误处理
仅使用标准库中的方法
如有必要,为文件创建目录
使用 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")
}
2019 年更新:
总结 - Java NIO(或异步的 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()
}
导入 Java 库:IO 和 NIO 使用您选择的文件名创建 Path 对象 将要插入文件的文本转换为字节数组 将文件作为流:OutputStream 将字节数组传递到输出流的写入函数关闭溪流
与 this answer 类似,以下是 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)
}
}
此行有助于从 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 文档 > Streaming File IO
不定期副业成功案例分享
Source
相同(默认为默认编码)。如果您发现这是一种常见需求,您当然可以在f
之后添加例如enc: Option[String] = None
参数。