Scala:在一个语句中将字符串写入文件


144

为了在Scala中读取文件,有

Source.fromFile("file.txt").mkString

是否有等效的简洁方法将字符串写入文件?

大多数语言都支持类似的东西。我最喜欢的是Groovy:

def f = new File("file.txt")
// Read
def s = f.text
// Write
f.text = "file contents"

我想将代码用于从单行到一小段代码的程序。不必使用自己的库在这里没有意义。我希望现代的语言可以让我方便地在文件中写一些东西。

有类似的帖子,但是他们没有回答我的确切问题,或者只关注较早的Scala版本。

例如:


看到这个问题。我同意评分最高的答案-最好拥有自己的个人图书馆。
ffriend

2
对于这种情况,我不同意必须编写自己的个人库。通常,当您编写一些小程序来做临时工作时(也许我只是想将其编写为单页scala脚本,也可以只写在REPL中)。然后访问个人图书馆变得很痛苦。
smartnut007

基本上,在这一点上,scala 2.9中似乎没有任何内容。不知道该如何打开这个问题。
smartnut007

1
如果在Scala源代码中搜索java.io,则不会发现很多情况,而对于输出操作(尤其是PrintWriter)则更少。因此,在Scala-IO库成为Scala的正式组成部分之前,我们必须使用纯Java,如范例所示。
PhiLho 2011年

是的,问题还需要与jdk7 IO改进兼容的scala-io。
smartnut007

Answers:


77

简洁的一行:

import java.io.PrintWriter
new PrintWriter("filename") { write("file contents"); close }

14
虽然这种方法看起来不错,但它既不是异常安全的,也不是编码安全的。如果中发生异常write()close将永远不会调用它,也不会关闭该文件。PrintWriter还使用默认的系统编码,这对于可移植性非常不利。最后,这种方法专门为该行生成了一个单独的类(但是,鉴于Scala即使对于简单的代码也已经生成了大量的类,这本身并不是一个缺点)。
弗拉基米尔·马特维耶夫

根据上面的评论,虽然这是一个班轮,但这是不安全的。如果您想在位置和/或缓冲输入周围有更多选择的同时提高安全性,请参阅我刚刚在类似线程上发布的答案:stackoverflow.com/a/34277491/501113
chaotic3quilibrium

2
对于临时调试日志,我使用了new PrintWriter(tmpFile) { try {write(debugmsg)} finally {close} }
Randall Whitman

从风格上讲,使用它可能更好close(),我认为
Casey

这是两行,可以很容易地做成这样的一行:new java.io.PrintWriter("/tmp/hello") { write("file contents"); close }
Philluminati

163

奇怪的是没有人建议使用NIO.2操作(自Java 7起可用):

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

Files.write(Paths.get("file.txt"), "file contents".getBytes(StandardCharsets.UTF_8))

我认为这是迄今为止最简单,最简单和最惯用的方式,并且在Java本身中不需要任何依赖项。


由于某些原因,这会忽略换行符。
卡卡吉

@Kakaji,能否请您详细说明?我刚刚使用带换行符的字符串对其进行了测试,并且效果很好。它只是不能过滤任何内容-Files.write()将字节数组写为blob,而不分析其内容。毕竟,在某些二进制数据中,0x0d字节可能具有除换行符以外的重要含义。
弗拉基米尔·马特维耶夫(Fladimir Matveev)

4
附加说明:如果您有一个文件,只需'.toPath'即可获取第一个参数。
akauppi '16

1
这是一个工业级的产品(由于使用了明确的CharSets选择),但是缺少该产品的简单性(以及一个衬里)reflect.io.File.writeAll(contents)。为什么?包含两个import语句时,三行。也许IDE会自动为您完成此操作,但是如果在REPL中,则并非如此。
javadba

3
@javadba我们在JVM中,导入几乎不算作“行”,尤其是因为替代方法几乎总是添加新的库依赖项。无论如何,Files.write也接受a java.lang.Iterable作为第二个参数,我们可以Iterable使用转换器从Scala (即几乎任何集合类型)中获取该参数:import java.nio.file.{Files, Paths}; import scala.collection.JavaConverters.asJavaIterableConverter; val output = List("1", "2", "3"); Files.write(Paths.get("output.txt"), output.asJava)
Yawar,

83

这是使用reflect.io.file的简洁代码,可与Scala 2.12一起使用:

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

另外,如果您想使用Java库,可以执行以下操作:

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

1
+1很棒。该Some/ foreach组合是有点古怪,但它与没有的try / catch的/利益终于。
布伦特·浮士德·浮士德

3
好吧,如果写入引发异常,如果您打算从异常中恢复并再次读取/写入文件,则可能要关闭文件。幸运的是,scala也提供一线执行此操作。
2013年

25
不建议这样做,因为scala.tools.nsc.io包不是公共API的一部分,而是由编译器使用的。
Giovanni Botta 2014年

3
Some/ foreach黑客也正是为什么很多人讨厌斯卡拉它使黑客产生不可读的代码。
埃里克·卡普伦

3
scala.tootls.nsc.io.File是的别名reflect.io.File。仍然是内部API,但至少短一些。
kostja

41

如果您喜欢Groovy语法,则可以使用Pimp-My-Library设计模式将其引入Scala:

import java.io._
import scala.io._

class RichFile( file: File ) {

  def text = Source.fromFile( file )(Codec.UTF8).mkString

  def text_=( s: String ) {
    val out = new PrintWriter( file , "UTF-8")
    try{ out.print( s ) }
    finally{ out.close }
  }
}

object RichFile {

  implicit def enrichFile( file: File ) = new RichFile( file )

}

它将按预期工作:

scala> import RichFile.enrichFile
import RichFile.enrichFile

scala> val f = new File("/tmp/example.txt")
f: java.io.File = /tmp/example.txt

scala> f.text = "hello world"

scala> f.text
res1: String = 
"hello world

2
您永远不会在Source.fromFile返回的实例上调用close,这意味着直到资源被GCed(垃圾回收)后,资源才关闭。而且您的PrintWriter未被缓冲,因此它使用的是8个字节的JVM默认微型缓冲区,这可能会大大降低IO的速度。我刚刚在处理这些问题的类似线程上创建了一个答案:stackoverflow.com/a/34277491/501113
chaotic3quilibrium

1
你是对的。但是,我在这里给出的解决方案对于具有小IO的短期程序非常有效。我不建议将其用于服务器进程或大型数据(通常,超过500 MB)。
范式

23
import sys.process._
"echo hello world" #> new java.io.File("/tmp/example.txt") !

在REPL中,这对我不起作用。没有错误消息,但如果我查看/tmp/example.txt,则不会。
用户未知

@user未知,很抱歉错过了'!' 在命令末尾,现在已修复。
xiefei

精彩!现在它可以工作了,我想知道为什么。什么是#>,怎么!办?
用户未知

10
此解决方案不可移植!仅适用于* nix系统。
Giovanni Botta 2014年

2
这不适用于编写任意字符串。它仅适用于您可以作为参数传递给命令行echo工具的短字符串。
Rich

15

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

file.write("Hi!")

要么

file << "Hi!"

3
爱你的图书馆!这个问题是在谷歌搜索如何使用Scala编写文件时的热门话题之一-既然您的项目越来越大,您可能想扩大答案吗?
asac

12

您可以轻松使用Apache File Utils。看功能writeStringToFile。我们在项目中使用此库。


3
我也一直使用它。如果您仔细阅读了问题,我已经提到了为什么我不想使用图书馆。
smartnut007

在不使用库的情况下,我创建了一个解决方案,该方案可以处理读/写期间的异常,并且可以超出Java库提供的微小缓冲区默认值进行缓冲:stackoverflow.com/a/34277491/501113
chaotic3quilibrium,2015年

7

一种也具有这种格式,既简洁又简洁,底层库编写精美(请参见源代码):

import scalax.io.Codec
import scalax.io.JavaConverters._

implicit val codec = Codec.UTF8

new java.io.File("hi-file.txt").asOutput.write("I'm a hi file! ... Really!")

7

我想这很简洁:

scala> import java.io._
import java.io._

scala> val w = new BufferedWriter(new FileWriter("output.txt"))
w: java.io.BufferedWriter = java.io.BufferedWriter@44ba4f

scala> w.write("Alice\r\nBob\r\nCharlie\r\n")

scala> w.close()

4
足够公平,但是这种“足够简洁”并不能归类为“一个陈述”:P
Erik Kaplun 2014年

该代码使Java的许多公认问题成为可能。不幸的是,Scala认为IO不够重要,因此标准库不包含IO。
克里斯

答案隐藏了新FileWriter的孤立资源问题。如果新的FileWriter成功,但是新的BufferedWriter失败,则FileWriter实例将再也看不到,并且一直挂着打开,直到GCed(垃圾回收)为止,并且可能直到那时都没有关闭(由于finalize确保JVM中的工作)。我已经写了一个解决这些问题的类似问题的答案:stackoverflow.com/a/34277491/501113
chaotic3quilibrium

2

您可以混合使用Java和Scala库来完成此操作。您将完全控制字符编码。但不幸的是,文件句柄将无法正确关闭。

scala> import java.io.ByteArrayInputStream
import java.io.ByteArrayInputStream

scala> import java.io.FileOutputStream
import java.io.FileOutputStream

scala> BasicIO.transferFully(new ByteArrayInputStream("test".getBytes("UTF-8")), new FileOutputStream("test.txt"))

您的代码中有一个孤立的资源实例问题。由于您没有在调用之前捕获实例,因此,如果其中一个在调用的方法已传递参数之前引发了异常,则成功实例化的资源不会在GCed(垃圾回收)之前被关闭,甚至则可能不是由于GC保证工作的方式。我已经写了一个解决这些问题的类似问题的答案:stackoverflow.com/a/34277491/501113
chaotic3quilibrium

1
您说对了,您的解决方案也不错。但是这里的要求是一行完成的。当您仔细阅读时,我在回答中提到了资源泄漏,这是此要求和方法所带来的限制。您的解决方案很好,但不符合这一要求。
stefan.schwetschke 2015年

2

我知道这不是一条线,但据我所知,它可以解决安全问题。

// This possibly creates a FileWriter, but maybe not
val writer = Try(new FileWriter(new File("filename")))
// If it did, we use it to write the data and return it again
// If it didn't we skip the map and print the exception and return the original, just in-case it was actually .write() that failed
// Then we close the file
writer.map(w => {w.write("data"); w}).recoverWith{case e => {e.printStackTrace(); writer}}.map(_.close)

如果您不关心异常处理,则可以编写

writer.map(w => {w.writer("data"); w}).recoverWith{case _ => writer}.map(_.close)

1

更新:从那时起,我创建了一个更有效的解决方案,在这里我对其进行了详细说明:https : //stackoverflow.com/a/34277491/501113

我发现自己在Eclipse的Scala IDE的Scala工作表中工作的越来越多(我相信IntelliJ IDEA中有等同的功能)。无论如何,当我得到“输出超出临界值”时,我需要能够做一个单线输出一些内容。如果我正在做重要的事情,尤其是Scala系列,则显示一条消息。

我想出了一个单行代码,我将它插入到每个新的Scala工作表的顶部以简化此操作(因此,我不必为了非常简单的需要而进行整个外部库导入练习)。如果您是一个顽固的人,并且注意到它在技术上是两行,则只是使它在此论坛中更具可读性。这是我的Scala工作表中的一行。

def printToFile(content: String, location: String = "C:/Users/jtdoe/Desktop/WorkSheet.txt") =
  Some(new java.io.PrintWriter(location)).foreach{f => try{f.write(content)}finally{f.close}}

用法很简单:

printToFile("A fancy test string\ncontaining newlines\nOMG!\n")

这样,如果我想拥有除默认值以外的其他文件(每次调用该方法时都会完全覆盖该文件),则可以选择提供文件名。

因此,第二种用法很简单:

printToFile("A fancy test string\ncontaining newlines\nOMG!\n", "C:/Users/jtdoe/Desktop/WorkSheet.txt")

请享用!


1

使用炸药操作库。语法非常简单,但是该库的宽度几乎与使用bash这样的shell脚本语言尝试这种任务所期望的宽度一样宽。

在我链接到的页面上,它显示了可以对库执行的许多操作,但是为了回答这个问题,这是一个写入文件的示例

import ammonite.ops._
write(pwd/'"file.txt", "file contents")

-1

通过分号的魔力,您可以将任何东西变成单线。

import java.io.PrintWriter
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.charset.StandardCharsets
import java.nio.file.StandardOpenOption
val outfile = java.io.File.createTempFile("", "").getPath
val outstream = new PrintWriter(Files.newBufferedWriter(Paths.get(outfile)
  , StandardCharsets.UTF_8
  , StandardOpenOption.WRITE)); outstream.println("content"); outstream.flush(); outstream.close()

这里没有争论。我决定不去记住哪些API需要我充实自己的生活,所以我总是这样做。
离子弗里曼
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.