在 Kotlin 中,如何将 InputStream 的全部内容读入字符串?

Posted

技术标签:

【中文标题】在 Kotlin 中,如何将 InputStream 的全部内容读入字符串?【英文标题】:In Kotlin, how do I read the entire contents of an InputStream into a String? 【发布时间】:2017-01-22 19:50:37 【问题描述】:

我最近在 Kotlin 中看到了将 InputStream 的全部内容读入字符串的代码,例如:

// 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 的非笨拙(惯用)方式是什么?

注意: 此问题由作者 (Self-Answered Questions) 有意编写和回答,因此常见的 Kotlin 主题的惯用答案出现在 SO 中。

【问题讨论】:

【参考方案1】:

Kotlin 有一个专门用于此目的的扩展。

最简单的:

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

在此示例中,您可以在bufferedReader() 或仅reader() 之间做出选择。对函数 Closeable.use() 的调用将在 lambda 执行结束时自动关闭输入。

进一步阅读:

如果你经常做这种事情,你可以把它写成一个扩展函数:

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

然后您可以轻松调用:

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

附带说明,所有需要知道 charset 的 Kotlin 扩展函数已经默认为 UTF-8,因此如果您需要不同的编码,您需要在调用中调整上面的代码以包含 reader(charset) 的编码或bufferedReader(charset).

警告:您可能会看到更短的示例:

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

但是这些不会关闭流。确保检查您使用的API documentation for all of the IO functions 以确定哪些关闭,哪些不关闭。通常,如果它们包含单词use(例如useLines()use()),它们会在之后关闭流。一个例外是 File.readText()Reader.readText() 的不同之处在于前者不会打开任何东西,而后者确实需要显式关闭。

另请参阅: Kotlin IO related extension functions

【讨论】:

对于您提出的扩展功能,我认为“readText”会比“useText”更好。当我阅读“useText”时,我希望有一个像useuseLines 这样的函数,它对正在“使用”的内容执行一个块函数。例如inputStream.useText text -> ... 另一方面,当我阅读“readText”时,我希望有一个返回文本的函数:val inputAsString = inputStream.readText() 是的,但是 readText 已经有错误的含义,所以想表明它在这方面更像use 函数。至少在本问答的背景下。也许可以找到一个新的动词...... @mfulton26 我在这个例子中选择了readTextAndClose() 以避免与readText() 不关闭的模式和use 想要一个 lambda 的模式发生冲突,因为我不想引入一个新的 stdlib 函数我不想做的只是说明使用扩展来节省未来的劳动力。 @JaysonMinard 为什么不将此标记为答案?不过很好:-) 自 Kotlin 1.3 以来,readBytes + String 构造函数可以说更简单,但它仅限于 2GB,因此可能不值得推荐(因此此评论,而不是答案):inputStream.use String(it.readBytes()) 。默认为 UTF-8,但您也可以将编码传递给 String 构造函数。尽管如此,如果阅读文本,使用InputStreamReader 似乎是一个好习惯,即使小于 2GB,这可能是大部分时间。【参考方案2】:

方法一 |手动关闭流

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

inputStream.readBytes() 需要手动关闭流:https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/java.io.-input-stream/read-bytes.html

方法二 |自动关闭流

private fun getFileText(uri: Uri): String 
    return contentResolver.openInputStream(uri)!!.bufferedReader().use it.readText() 

你可以指定bufferedReader()里面的字符集,默认是UTF-8: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/java.io.-input-stream/buffered-reader.html

bufferedReader()reader()的升级版,用途更广: How exactly does bufferedReader() work in Kotlin?

use() 可以在块完成时自动关闭流: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/use.html

【讨论】:

为了改进答案,请给出一些叙述来解释这是如何工作的 你为什么在你的例子之间混合readBytesreadText?自动关闭流由use 处理,BufferedReader/readTextreadBytes 的选择与此无关。第二个示例也与已接受答案顶部的示例相同。【参考方案3】:

将 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 Read File

【讨论】:

您的示例包含缺陷:1) 对于跨平台路径,您应该使用Paths.get() 方法。 2) 对于流 - try-resource 功能(在 kotlin 中:.use )【参考方案4】:

快速解决方案在将 InputStream 转换为字符串时效果很好。

val convertedInputStream = String(inputStream.readAllBytes(), StandardCharsets.UTF_8)

【讨论】:

InputStream 上没有 readAllBytes 方法。也许你的意思是readBytes?如果是这样,那么您还需要关闭流。

以上是关于在 Kotlin 中,如何将 InputStream 的全部内容读入字符串?的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin:如何将对象列表插入到 Room 中?

如何读取将作为 MultipartFile 参数传递的 SQLite 文件? (在 java/kotlin 中)

如何将 Kotlin 错误链接到 Android Studio 中的源?

如何在 Android 的 Kotlin 中使用 GSON 将 JSON 对象转换为 JSON 数组

如何在 Kotlin 中解析 JSON?

如何将 java 库添加到 kotlin 本机