En Kotlin, ¿cómo leo todo el contenido de un InputStream en un String?

105

Recientemente vi código para leer el contenido completo de InputStreamuna cadena en Kotlin, como:

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

Y también:

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 incluso esto que parece más suave ya que cierra automáticamente el InputStream:

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

O una ligera variación en ese:

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

Entonces esta cosa de pliegue funcional:

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

O una mala variación que no cierra el InputStream:

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

Pero todos son torpes y sigo encontrando versiones más nuevas y diferentes de los mismos ... y algunos de ellos ni siquiera cierran el InputStream. ¿Cuál es una forma no torpe (idiomática) de leer el InputStream?

Nota: esta pregunta fue escrita y respondida intencionalmente por el autor ( Preguntas auto- respondidas ), de modo que las respuestas idiomáticas a los temas de Kotlin más frecuentes estén presentes en SO.

Jayson Minard
fuente

Respuestas:

216

Kotlin tiene extensiones específicas solo para este propósito.

Lo más simple:

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

Y en este ejemplo podría decidir entre bufferedReader()o simplemente reader(). La llamada a la funciónCloseable.use() cerrará automáticamente la entrada al final de la ejecución de lambda.

Otras lecturas:

Si hace mucho este tipo de cosas, podría escribir esto como una función de extensión:

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

Que luego podría llamar fácilmente como:

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

En una nota al margen, todas las funciones de extensión de Kotlin que requieren conocer el charsetvalor predeterminado paraUTF-8 , por lo que si necesita una codificación diferente, debe ajustar el código anterior en las llamadas para incluir una codificación para reader(charset)o bufferedReader(charset).

Advertencia: es posible que vea ejemplos más cortos:

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

Pero estos no cierran el arroyo . Asegúrese de consultar la documentación de la API para todas las funciones de E / S que utiliza para asegurarse de cuáles cierran y cuáles no. Por lo general, si incluyen la palabra use(como useLines()o use()), cierra el flujo después. Una excepción es queFile.readText() difiere deReader.readText() en que el primero no deja nada abierto y el segundo sí requiere un cierre explícito.

Ver también: Funciones de extensión relacionadas con Kotlin IO

Jayson Minard
fuente
1
Creo que "readText" sería un nombre mejor que "useText" para la función de extensión que propones. Cuando leo "useText", espero una función como useo useLinesque ejecuta una función de bloque sobre lo que se está "usando". por ejemplo, inputStream.useText { text -> ... }Por otra parte, cuando leí "READTEXT" Me espera una función que devuelve el texto: val inputAsString = inputStream.readText().
mfulton26
Es cierto, pero readText ya tiene un significado incorrecto, así que quería indicar que era más como las usefunciones en ese sentido. al menos en el contexto de estas preguntas y respuestas. tal vez se pueda encontrar un nuevo verbo ...
Jayson Minard
3
@ mfulton26 Fui con readTextAndClose()este ejemplo para evitar conflictos con los readText()patrones de no cerrar y con los usepatrones que quieren una lambda, ya que no estoy tratando de introducir una nueva función stdlib, no quiero hacer más que señalar el uso extensiones para ahorrar mano de obra futura.
Jayson Minard
@JaysonMinard, ¿por qué no marca esto como respuesta? aunque es genial :-)
piotrek1543
2

Un ejemplo que lee el contenido de un InputStream en un 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)
}

Para referencia: archivo de lectura de Kotlin

Mallikarjun M
fuente
1
Su ejemplo contiene fallas: 1) Para rutas multiplataforma, debe usar el Paths.get()método. 2) Para transmisiones: función try-resource (en kotlin: .use {})
Evgeny Lebedev