W Kotlinie, jak odczytać całą zawartość InputStream w String?

106

Niedawno widziałem kod do wczytywania całej zawartości an InputStreaminto a String w Kotlinie, taki jak:

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

I również:

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

I nawet to wygląda płynniej, ponieważ automatycznie zamyka InputStream:

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

Lub niewielka zmiana tego:

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

Następnie ten funkcjonalny skład:

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

Lub zła odmiana, która nie zamyka InputStream:

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

Ale wszystkie są niezgrabne i ciągle znajduję nowsze i różne wersje tego samego ... a niektóre z nich nawet nie zamykają InputStream. Jaki jest niezgrabny (idiomatyczny) sposób czytania InputStream?

Uwaga: to pytanie jest celowo napisany i odpowiedział przez autora ( Self-odpowiedziało na pytania ), tak że idiomatyczne odpowiedzi na najczęściej zadawane tematy Kotlin są obecne w tak.

Jayson Minard
źródło

Odpowiedzi:

219

Kotlin ma specjalne rozszerzenia przeznaczone tylko do tego celu.

Najprostszy:

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

W tym przykładzie możesz zdecydować między bufferedReader()lub tylko reader(). Wywołanie funkcji Closeable.use()automatycznie zamknie dane wejściowe pod koniec wykonywania lambdy.

Dalsze czytanie:

Jeśli często robisz tego typu rzeczy, możesz zapisać to jako funkcję rozszerzającą:

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

Które możesz wtedy łatwo zadzwonić jako:

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

Na marginesie, wszystkie funkcje rozszerzeń Kotlin, które wymagają znajomości charsetjuż domyślnego UTF-8, więc jeśli potrzebujesz innego kodowania, musisz dostosować powyższy kod w wywołaniach, aby uwzględnić kodowanie dla reader(charset)lub bufferedReader(charset).

Ostrzeżenie: możesz zobaczyć przykłady, które są krótsze:

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

Ale te nie zamykają strumienia . Upewnij się, że sprawdziłeś dokumentację API dla wszystkich używanych funkcji IO , aby upewnić się, które z nich są zamknięte, a które nie. Zwykle, jeśli zawierają słowo use(takie jak useLines()lub use()), zamykasz strumień po. Wyjątkiem jest to, że File.readText()różni się od Reader.readText()tego, że pierwszy nie pozostawia niczego otwartego, a drugi rzeczywiście wymaga wyraźnego zamknięcia.

Zobacz także: funkcje rozszerzeń związane z Kotlin IO

Jayson Minard
źródło
1
Myślę, że „readText” byłoby lepszą nazwą niż „useText” dla proponowanej funkcji rozszerzającej. Kiedy czytam „useText”, spodziewam się funkcji takiej jak uselub, useLinesktóra wykonuje funkcję blokową na tym, co jest „używane”. np inputStream.useText { text -> ... }Z drugiej strony, kiedy czytam „READTEXT” Spodziewam funkcję, która zwraca tekst: val inputAsString = inputStream.readText().
mfulton26
To prawda, ale readText ma już niewłaściwe znaczenie, więc chciałem zaznaczyć, że bardziej przypomina usefunkcje w tym względzie. przynajmniej w kontekście tego pytania i odpowiedzi. może można znaleźć nowy czasownik ...
Jayson Minard
3
@ mfulton26 W readTextAndClose()tym przykładzie wybrałem, aby uniknąć konfliktów z readText()wzorcami niezamykania iz wzorami wymagającymi uselambdy, ponieważ nie próbuję wprowadzać nowej funkcji standardowej biblioteki, nie chcę robić nic więcej niż tylko zwrócić uwagę na użycie rozszerzenia, aby zaoszczędzić przyszłą siłę roboczą.
Jayson Minard
@JaysonMinard, dlaczego nie oznaczysz tego jako odpowiedzi? chociaż jest super :-)
piotrek1543
2

Przykład odczytujący zawartość InputStream do 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)
}

Dla odniesienia - Kotlin Read File

Mallikarjun M
źródło
1
Twój przykład ma wady: 1) W przypadku ścieżek międzyplatformowych należy użyć Paths.get()metody method. 2) Dla strumieni - funkcja try-resource (w kotlin: .use {})
Evgeny Lebedev,
0

Metoda 1 | Ręcznie zamknij strumień

private fun getFileText(uri: Uri):String {
    val inputStream = contentResolver.openInputStream(uri)!!

    val bytes = inputStream.readBytes()        //see below

    val text = String(bytes, StandardCharsets.UTF_8)    //specify charset

    inputStream.close()

    return text
}

Metoda 2 | Automatycznie zamknij strumień

private fun getFileText(uri: Uri): String {
    return contentResolver.openInputStream(uri)!!.bufferedReader().use {it.readText() }
}
Sam Chen
źródło
aby poprawić odpowiedź, podaj trochę narracji wyjaśniającej, jak to działa
Kirby