Как в Kotlin прочитать все содержимое InputStream в строку?

106

Недавно я видел код для чтения всего содержимого InputStreamстроки в Kotlin, например:

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

А также:

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

И даже это выглядит более плавным, поскольку автоматически закрывает InputStream:

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

Или небольшое изменение этого:

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

Затем эта функциональная складчатая штуковина:

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

Или плохой вариант, который не закрывает InputStream:

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

Но все они неуклюжие, и я постоянно нахожу новые и разные версии одного и того же ... а некоторые из них даже не закрывают InputStream. Какой неуклюжий (идиоматический) способ читать InputStream?

Примечание: этот вопрос намеренно написан и на него дан ответ автора ( вопросы с ответами), так что идиоматические ответы на часто задаваемые темы Kotlin присутствуют в SO.

Джейсон Минард
источник

Ответы:

219

В Kotlin есть специальные расширения только для этой цели.

Простейший:

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

И в этом примере вы можете выбрать между bufferedReader()или просто reader(). Вызов функции Closeable.use()автоматически закроет ввод в конце выполнения лямбды.

Дальнейшее чтение:

Если вы часто занимаетесь подобными вещами, вы можете написать это как функцию расширения:

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

Который затем можно было бы легко назвать как:

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

С другой стороны, все функции расширения Kotlin, для которых требуется знать charsetзначение по умолчанию UTF-8, поэтому, если вам требуется другая кодировка, вам необходимо настроить приведенный выше код в вызовах, чтобы включить кодировку для reader(charset)или bufferedReader(charset).

Предупреждение: вы можете увидеть примеры короче:

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

Но это не закрывает поток . Убедитесь, что вы проверили документацию API для всех функций ввода-вывода, которые вы используете, чтобы убедиться, какие из них закрываются, а какие нет. Обычно, если они включают слово use(например, useLines()или use()), то закрывают поток после. Исключением является то , что File.readText()отличается от Reader.readText()в том , что бывший не оставляет ничего открытым и последний действительно требуют явного закрытия.

См. Также: Функции расширения, связанные с Kotlin IO

Джейсон Минард
источник
1
Я думаю, что «readText» было бы лучшим названием, чем «useText» для функции расширения, которую вы предлагаете. Когда я читаю «useText», я ожидаю, что функция, подобная useor, useLinesвыполняет функцию блока для того, что «используется». например , inputStream.useText { text -> ... }С другой стороны, когда я прочитал «READTEXT» Я ожидаю , что функция , которая возвращает текст: val inputAsString = inputStream.readText().
mfulton26
Верно, но readText уже имеет неправильное значение, поэтому хотел показать, что useв этом отношении он больше похож на функции. по крайней мере, в контексте этих вопросов и ответов. может быть найден новый глагол ...
Джейсон Минард
3
@ mfulton26 Я readTextAndClose()использовал этот пример, чтобы избежать конфликта с readText()шаблонами незакрытия и с useшаблонами, требующими лямбда, поскольку я не пытаюсь ввести новую функцию stdlib, я не хочу делать больше, чем указывать на использование расширения, чтобы сэкономить труд в будущем.
Джейсон Минард
@JaysonMinard, почему бы тебе не отметить это как ответ? хотя это здорово :-)
piotrek1543
2

Пример, который считывает содержимое InputStream в строку

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

Для справки - файл чтения Kotlin

Малликарджун М
источник
1
Ваш пример содержит недостатки: 1) Для кросс-платформенных путей вы должны использовать Paths.get()метод. 2) Для потоков - функция try-resource (В котлине: .use {})
Евгений Лебедев
0

Метод 1 | Закрыть поток вручную

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
}

Метод 2 | Автоматически закрывать поток

private fun getFileText(uri: Uri): String {
    return contentResolver.openInputStream(uri)!!.bufferedReader().use {it.readText() }
}
Сэм Чен
источник
чтобы улучшить ответ, опишите, как это работает
Кирби