Wie lese ich in Kotlin den gesamten Inhalt eines InputStreams in einen String?

105

Ich habe kürzlich Code zum Lesen des gesamten Inhalts InputStreameines Strings in Kotlin gesehen, wie zum Beispiel:

// input is of type InputStream
val baos = ByteArrayOutputStream()
input.use { it.copyTo(baos) }
val inputAsString = baos.toString()

Und auch:

val reader = BufferedReader(InputStreamReader(input))
try {
    val results = StringBuilder()
    while (true) { 
        val line = reader.readLine()
        if (line == null) break
        results.append(line) 
    }
    val inputAsString = results.toString()
} finally {
    reader.close()
}

Und selbst das sieht flüssiger aus, da es automatisch schließt InputStream:

val inputString = BufferedReader(InputStreamReader(input)).useLines { lines ->
    val results = StringBuilder()
    lines.forEach { results.append(it) }
    results.toString()
}

Oder eine leichte Variation davon:

val results = StringBuilder()
BufferedReader(InputStreamReader(input)).forEachLine { results.append(it) }
val resultsAsString = results.toString()   

Dann dieses funktionale Faltding:

val inputString = input.bufferedReader().useLines { lines ->
    lines.fold(StringBuilder()) { buff, line -> buff.append(line) }.toString()
}

Oder eine schlechte Variante, die das nicht schließt InputStream:

val inputString = BufferedReader(InputStreamReader(input))
        .lineSequence()
        .fold(StringBuilder()) { buff, line -> buff.append(line) }
        .toString()

Aber sie sind alle klobig und ich finde immer wieder neuere und unterschiedliche Versionen derselben ... und einige von ihnen schließen die nicht einmal InputStream. Was ist eine nicht klobige (idiomatische) Art, das zu lesen InputStream?

Hinweis: Diese Frage wurde absichtlich vom Autor geschrieben und beantwortet ( Selbst beantwortete Fragen ), sodass die idiomatischen Antworten auf häufig gestellte Kotlin-Themen in SO vorhanden sind.

Jayson Minard
quelle

Antworten:

215

Kotlin hat speziell für diesen Zweck spezielle Erweiterungen.

Das einfachste:

val inputAsString = input.bufferedReader().use { it.readText() }  // defaults to UTF-8

Und in diesem Beispiel können Sie sich zwischen bufferedReader()oder nur entscheiden reader(). Der Aufruf der Funktion Closeable.use()schließt die Eingabe am Ende der Lambda-Ausführung automatisch.

Weiterführende Literatur:

Wenn Sie so etwas häufig tun, können Sie dies als Erweiterungsfunktion schreiben:

fun InputStream.readTextAndClose(charset: Charset = Charsets.UTF_8): String {
    return this.bufferedReader(charset).use { it.readText() }
}

Was Sie dann leicht nennen könnten als:

val inputAsString = input.readTextAndClose()  // defaults to UTF-8

Nebenbei bemerkt, alle Kotlin-Erweiterungsfunktionen, für die die Kenntnis der charsetbereits vorhandenen Standardeinstellung UTF-8erforderlich ist. Wenn Sie also eine andere Codierung benötigen, müssen Sie den obigen Code in Aufrufen anpassen, um eine Codierung für reader(charset)oder einzuschließen bufferedReader(charset).

Warnung: Möglicherweise werden kürzere Beispiele angezeigt:

val inputAsString = input.reader().readText() 

Aber diese schließen den Strom nicht . Stellen Sie sicher, dass Sie die API-Dokumentation für alle von Ihnen verwendeten E / A-Funktionen überprüfen , um sicherzustellen, welche geschlossen sind und welche nicht. Normalerweise, wenn sie das Wort use(wie useLines()oder use()) enthalten, schließen Sie den Strom danach. Eine Ausnahme ist, dass sie sich dadurch File.readText()unterscheidet, Reader.readText()dass erstere nichts offen lässt und letztere tatsächlich ein explizites Schließen erfordert.

Siehe auch: Kotlin IO-bezogene Erweiterungsfunktionen

Jayson Minard
quelle
1
Ich denke, "readText" wäre ein besserer Name als "useText" für die von Ihnen vorgeschlagene Erweiterungsfunktion. Wenn ich "useText" lese, erwarte ich eine Funktion wie useoder useLines, die eine Blockfunktion für das ausführt, was "verwendet" wird. inputStream.useText { text -> ... }Zum Beispiel erwarte ich beim Lesen von "readText" eine Funktion, die den Text zurückgibt : val inputAsString = inputStream.readText().
mfulton26
Stimmt, aber readText hat bereits die falsche Bedeutung, wollte also bedeuten, dass es eher den useFunktionen in dieser Hinsicht entspricht. Zumindest im Zusammenhang mit diesen Fragen und Antworten. Vielleicht kann ein neues Verb gefunden werden ...
Jayson Minard
3
@ mfulton26 Ich habe mich readTextAndClose()für dieses Beispiel entschieden, um Konflikte mit readText()Mustern zu vermeiden , die nicht geschlossen werden, und mit useMustern, die ein Lambda wollen, da ich nicht versuche, eine neue stdlib-Funktion einzuführen, möchte ich nicht mehr tun, als einen Punkt über die Verwendung zu machen Erweiterungen, um zukünftige Arbeitskräfte zu sparen.
Jayson Minard
@JaysonMinard warum markierst du das nicht als Antwort? es ist aber großartig :-)
piotrek1543
2

Ein Beispiel, das den Inhalt eines InputStream in einen String liest

import java.io.File
import java.io.InputStream
import java.nio.charset.Charset

fun main(args: Array<String>) {
    val file = File("input"+File.separator+"contents.txt")
    var ins:InputStream = file.inputStream()
    var content = ins.readBytes().toString(Charset.defaultCharset())
    println(content)
}

Als Referenz - Kotlin Read File

Mallikarjun M.
quelle
Ihr Beispiel enthält Fehler: 1) Für plattformübergreifende Pfade sollten Sie die Paths.get()Methode verwenden. 2) Für Streams - Try-Resource-Funktion (In Kotlin: .use {})
Evgeny Lebedev