Dans Kotlin, comment lire tout le contenu d'un InputStream dans une chaîne?

105

J'ai récemment vu du code pour lire le contenu entier d'un InputStreamdans une chaîne dans Kotlin, tel que:

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

Et aussi:

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

Et même celui-ci qui semble plus lisse car il ferme automatiquement le InputStream:

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

Ou une légère variation sur celui-là:

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

Ensuite, ce truc de pli fonctionnel:

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

Ou une mauvaise variation qui ne ferme pas le InputStream:

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

Mais ils sont tous maladroits et je continue à trouver des versions plus récentes et différentes du même ... et certains d'entre eux ne ferment même jamais le InputStream. Qu'est-ce qu'une façon non maladroite (idiomatique) de lire le InputStream?

Remarque: cette question est intentionnellement écrite et répondue par l'auteur ( Questions auto-répondues ), de sorte que les réponses idiomatiques aux sujets Kotlin fréquemment posées soient présentes dans SO.

Jayson Minard
la source

Réponses:

217

Kotlin a des extensions spécifiques juste à cet effet.

Le plus simple:

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

Et dans cet exemple, vous pouvez choisir entre bufferedReader()ou simplement reader(). L'appel à la fonction Closeable.use()fermera automatiquement l'entrée à la fin de l'exécution du lambda.

Lectures complémentaires:

Si vous faites beaucoup ce type de chose, vous pouvez l'écrire comme une fonction d'extension:

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

Que vous pourriez alors appeler facilement comme:

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

Sur une note latérale, toutes les fonctions d'extension Kotlin qui nécessitent de connaître charsetdéjà la valeur par défaut UTF-8, donc si vous avez besoin d'un encodage différent, vous devez ajuster le code ci-dessus dans les appels pour inclure un encodage pour reader(charset)ou bufferedReader(charset).

Avertissement: vous pouvez voir des exemples plus courts:

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

Mais ceux - ci ne ferment pas le flux . Assurez-vous de consulter la documentation de l' API pour toutes les fonctions IO que vous utilisez pour être sûr de celles qui se ferment et de celles qui ne le font pas. Habituellement, s'ils incluent le mot use(tel que useLines()ou use()), vous fermez le flux après. Une exception est que cela File.readText()diffère du fait Reader.readText()que le premier ne laisse rien ouvert et le second nécessite en effet une fermeture explicite.

Voir aussi: Fonctions d'extension liées à Kotlin IO

Jayson Minard
la source
1
Je pense que "readText" serait un meilleur nom que "useText" pour la fonction d'extension que vous proposez. Quand je lis "useText", j'attends une fonction comme useou useLinesqui exécute une fonction de bloc sur ce qui est "utilisé". par exemple , d' inputStream.useText { text -> ... }autre part, quand je lis « readText » Je me attends à une fonction qui renvoie le texte: val inputAsString = inputStream.readText().
mfulton26
C'est vrai, mais readText a déjà la mauvaise signification, donc je voulais signifier qu'il ressemblait plus aux usefonctions à cet égard. au moins dans le contexte de ce Q&R. peut-être qu'un nouveau verbe peut être trouvé ...
Jayson Minard
3
@ mfulton26 Je suis allé avec readTextAndClose()cet exemple pour éviter les conflits avec les readText()modèles de non fermeture, et avec les usemodèles voulant un lambda, puisque je n'essaye pas d'introduire une nouvelle fonction stdlib, je ne veux pas faire plus que faire un point sur l'utilisation extensions pour économiser le travail futur.
Jayson Minard
@JaysonMinard pourquoi ne pas marquer ceci comme réponse? c'est super bien :-)
piotrek1543
2

Un exemple qui lit le contenu d'un InputStream dans une chaîne

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

Pour référence - Fichier de lecture Kotlin

Mallikarjun M
la source
1
Votre exemple contient des défauts: 1) Pour les chemins multiplateformes, vous devez utiliser la Paths.get()méthode. 2) Pour les flux - fonctionnalité try-resource (Dans kotlin: .use {})
Evgeny Lebedev