In Kotlin, come leggo l'intero contenuto di un InputStream in una stringa?


105

Recentemente ho visto il codice per leggere l'intero contenuto di un InputStreamin una stringa in Kotlin, come ad esempio:

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

E anche:

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

E anche questo sembra più fluido poiché chiude automaticamente InputStream:

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

O una leggera variazione su quello:

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

Quindi questa cosa pieghevole funzionale:

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

O una brutta variazione che non chiude il InputStream:

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

Ma sono tutti goffi e continuo a trovare versioni nuove e diverse dello stesso ... e alcuni di loro non chiudono nemmeno il file InputStream. Qual è un modo non goffo (idiomatico) di leggere il InputStream?

Nota: questa domanda è stata scritta intenzionalmente e l'autore ha risposto ( Self-Answered Questions ), in modo che le risposte idiomatiche agli argomenti di Kotlin più comuni siano presenti in SO.

Risposte:


216

Kotlin ha estensioni specifiche solo per questo scopo.

Il più semplice:

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

E in questo esempio potresti decidere tra bufferedReader()o solo reader(). La chiamata alla funzione Closeable.use()chiuderà automaticamente l'input alla fine dell'esecuzione del lambda.

Ulteriore lettura:

Se fai spesso questo tipo di cose, potresti scriverlo come una funzione di estensione:

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

Che potresti quindi chiamare facilmente come:

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

In una nota a margine, tutte le funzioni di estensione di Kotlin che richiedono la conoscenza del charsetgià predefinito UTF-8, quindi se hai bisogno di una codifica diversa, devi regolare il codice sopra nelle chiamate per includere una codifica per reader(charset)o bufferedReader(charset).

Avviso: potresti vedere esempi più brevi:

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

Ma questi non chiudono il flusso . Assicurati di controllare la documentazione API per tutte le funzioni IO che usi per essere sicuro quali chiudono e quali no. Di solito se includono la parola use(come useLines()o use()), chiudi il flusso dopo. Un'eccezione è che File.readText()differisce dal fatto Reader.readText()che il primo non lascia nulla di aperto e il secondo richiede infatti una chiusura esplicita.

Vedi anche: Funzioni di estensione relative a Kotlin IO


1
Penso che "readText" sarebbe un nome migliore di "useText" per la funzione di estensione che proponi. Quando leggo "useText" mi aspetto una funzione come useo useLinesche esegue una funzione di blocco su ciò che viene "utilizzato". ad esempio inputStream.useText { text -> ... }D'altra parte, quando ho letto "READTEXT" Mi aspetto una funzione che restituisce il testo: val inputAsString = inputStream.readText().
mfulton26

Vero, ma readText ha già il significato sbagliato, quindi volevo significare che era più simile alle usefunzioni in questo senso. almeno nel contesto di questa domanda e risposta. forse si può trovare un nuovo verbo ...
Jayson Minard

3
@ mfulton26 Sono andato con readTextAndClose()questo esempio per evitare conflitti con i readText()modelli di non chiusura e con i usemodelli che vogliono un lambda, dal momento che non sto cercando di introdurre una nuova funzione stdlib non voglio fare di più che sottolineare l'utilizzo estensioni per salvare il lavoro futuro.
Jayson Minard

@JaysonMinard perché non lo contrassegni come risposta? è fantastico però :-)
piotrek1543

2

Un esempio che legge il contenuto di un InputStream in una String

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

Per riferimento - Kotlin Leggi file


1
Il tuo esempio contiene difetti: 1) Per i percorsi multipiattaforma dovresti usare il Paths.get()metodo. 2) Per i flussi - funzione try-resource (In kotlin: .use {})
Evgeny Lebedev
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.