如何在Kotlin中写入文件?

70

我似乎还找不到这个问题,但是打开/创建一个文件,写入内容,然后关闭它的最简单、最典型的方法是什么?查看kotlin.io参考和Java文档,我得到了以下代码:

fun write() {
    val writer = PrintWriter("file.txt")  // java.io.PrintWriter

    for ((member, originalInput) in history) {  // history: Map<Member, String>
        writer.append("$member, $originalInput\n")
    }

    writer.close()
}

这个方法可行,但我想知道是否有一种“规范”的Kotlin方式来做到这一点?


2
我在新答案中添加了更多的变化,只是为了好玩,给你更多可以做的事情的想法。 - Jayson Minard
8个回答

125

更加符合惯用语。 对于PrintWriter,这个例子:

File("somefile.txt").printWriter().use { out ->
    history.forEach {
        out.println("${it.key}, ${it.value}")
    }
}

for循环或forEach,取决于你的编码风格。没有理由使用append(x),因为它基本上等同于write(x.toString()),而且你已经给出了一个字符串。如果xnullprintln(x)会在将其转换为"null"后执行write(x)。而println()会输出正确的行结尾。

如果你正在使用Kotlin的data类,它们可以直接输出,因为它们已经有了一个漂亮的toString()方法。

此外,在这种情况下,如果你想要使用BufferedWriter,它会产生相同的结果:

File("somefile.txt").bufferedWriter().use { out ->
    history.forEach {
        out.write("${it.key}, ${it.value}\n")
    }
}

如果您希望使其适用于当前操作系统,可以使用out.newLine()而不是\n。如果您一直在这样做,您可能会创建一个扩展函数:

fun BufferedWriter.writeLn(line: String) {
    this.write(line)
    this.newLine()
}

然后使用那个代替它:

File("somefile.txt").bufferedWriter().use { out ->
    history.forEach {
        out.writeLn("${it.key}, ${it.value}")
    }
}

这就是 Kotlin 的运作方式。更改 API 中的内容,使其成为您想要的样子。

有关此的不同解决方案,请参见另一个答案:https://dev59.com/6FsW5IYBdhLWcg3wDzrs#35462184


1
可能还有另一种解决方案:File(fileName).writeText(Iterable<Any>.joinToString(), Charsets.UTF_8) - Istvan Nagy
@IstvanNagy 这取决于可迭代对象的大小以及您是否想在写入之前将完整字符串实现。我会将其添加到备选答案中,这很好而且简短! - Jayson Minard
作为参考,由于我花了大约一个小时来弄清楚这个问题,如果你要多次打开和关闭文件以追加文本,请使用FileWriter("output.txt", true).use {作为第一行。(否则,每次打开时文本文件都会被清除) - takanuva15
这是否意味着printWriter没有使用任何缓冲区?这让我想知道为什么?在缓冲或拥有一个开箱即用的行写入函数之间做出决定感觉不对。 - bluenote10
1
不是这样的@bluenote10,那只是使用不同接口编写的另一个示例,并没有暗示什么。 - Jayson Minard

37

其他有趣的变化,让你看到 Kotlin 的强大:

通过一次性创建要写入的字符串来快速生成版本:

File("somefile.txt").writeText(history.entries.joinToString("\n") { "${it.key}, ${it.value}" })
// or just use the toString() method without transform:
File("somefile.txt").writeText(x.entries.joinToString("\n"))

假设你可能会执行其他功能,如筛选行或仅获取前100行等。您可以选择以下路线:

File("somefile.txt").printWriter().use { out ->
    history.map { "${it.key}, ${it.value}" }
           .filter { ... }
           .take(100)
           .forEach { out.println(it) }
}

或者,针对Iterable,通过创建扩展函数(类似于上面介绍的writeText()版本,但是将内容作为流而不是首先生成一个大字符串)允许将其转换为字符串并写入文件:

fun <T: Any> Iterable<T>.toFile(output: File, transform: (T)->String = {it.toString()}) {
    output.bufferedWriter().use { out ->
        this.map(transform).forEach { out.write(it); out.newLine() }
    }
}

fun <T: Any> Iterable<T>.toFile(outputFilename: String, transform: (T)->String = {it.toString()}) {
    this.toFile(File(outputFilename), transform)
}

可以用作以下任何一种:

history.entries.toFile(File("somefile.txt")) {  "${it.key}, ${it.value}" }

history.entries.toFile("somefile.txt") {  "${it.key}, ${it.value}" }

或者对每个项目使用默认的toString()方法:

history.entries.toFile(File("somefile.txt")) 

history.entries.toFile("somefile.txt") 

如果给定一个File,可以通过创建以下扩展函数从Iterable中填充它:

fun <T: Any> File.fillWith(things: Iterable<T>, transform: (T)->String = {it.toString()}) {
    this.bufferedWriter().use { out ->
        things.map(transform).forEach { out.write(it); out.newLine() }
    }
}

使用:

File("somefile.txt").fillWith(history.entries) { "${it.key}, ${it.value}" }

或对每个项使用默认的toString()方法:

File("somefile.txt").fillWith(history.entries) 

如果您已经有了另一个toFile扩展,您可以重写代码,让其中一个扩展调用另一个扩展:

fun <T: Any> File.fillWith(things: Iterable<T>, transform: (T)->String = {it.toString()}) {
    things.toFile(this, transform)
}

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/java.io.-file/write-text.html - Arto Bendiken

5

我认为这个看起来大致没问题。唯一不同的是,我会使用在ReadWrite中定义的“use”扩展来自动关闭写入器。

PrintWriter("file.txt").use {
  for ((member, originalInput) in history) {  // history: Map<Member, String>
    it.append("$member, $originalInput\n")
  }    
}

3

至少,你可以使用:

FileWriter(filename).use { it.write(text) }

FileWriter 是一个用于写字符文件的方便类(由 Java 提供,因此在 Kotlin 中可用)。它扩展了 Closeable,并且可以被 Kotlin 的 ".use" 扩展方法使用。

.use 扩展方法在代码块退出后自动关闭调用对象,从而提供了一种惯用的方式来关闭文件。


2
try{
      val fileWriter = FileWriter("test.txt", true)
      fileWriter.write(string+ "\n")
      fileWriter.close()
    } catch (exception: Exception){
        println(exception.message)
    }

1
例子很简单。
val path = context!!.filesDir.absolutePath // => /data/user/0/com.example.test/files
File("$path/filename.txt").writeText("hello")

1
一些 Kotlin 魔法允许在每次读取或写入时省略对流的引用:
fun <T : Closeable, R> T.useWith(block: T.() -> R): R = use { with(it, block) }

File("a.in").bufferedReader().useWith {
    File("a.out").printWriter().useWith {
        val (a, b) = readLine()!!.split(' ').map(String::toInt)
        println(a + b)
    }
}

Scanner(File("b.in")).useWith {
    PrintWriter("b.out").useWith {
        val a = nextInt()
        val b = nextInt()
        println(a + b)
    }
}

0

文件(requireContext().filesDir, "TodayTaskListChange.txt").writeText("在此编写您的测试...")


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接