Di Kotlin, bagaimana cara membaca seluruh konten InputStream menjadi String?

105

Saya baru-baru ini melihat kode untuk membaca seluruh konten an InputStreammenjadi String di Kotlin, seperti:

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

Dan juga:

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

Dan bahkan ini yang terlihat lebih halus karena menutup otomatis InputStream:

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

Atau sedikit variasi yang satu itu:

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

Kemudian lipat fungsional ini:

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

Atau variasi buruk yang tidak menutup InputStream:

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

Tapi mereka semua kikuk dan saya terus menemukan versi yang lebih baru dan berbeda dari yang sama ... dan beberapa dari mereka bahkan tidak pernah menutup InputStream. Apa cara non-kikuk (idiomatik) untuk membaca InputStream?

Catatan: pertanyaan ini sengaja ditulis dan dijawab oleh penulis ( Pertanyaan yang Dijawab Sendiri ), sehingga jawaban idiomatis untuk topik Kotlin yang sering ditanyakan ada di SO.

Jayson Minard
sumber

Jawaban:

217

Kotlin memiliki ekstensi khusus hanya untuk tujuan ini.

Yang paling sederhana:

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

Dan dalam contoh ini Anda dapat memutuskan antara bufferedReader()atau hanya reader(). Panggilan ke fungsi tersebut Closeable.use()akan secara otomatis menutup input di akhir eksekusi lambda.

Bacaan lebih lanjut:

Jika Anda sering melakukan hal semacam ini, Anda dapat menulis ini sebagai fungsi ekstensi:

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

Yang kemudian dapat Anda panggil dengan mudah seperti:

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

Di samping catatan, semua fungsi ekstensi Kotlin yang memerlukan pengetahuan charsetsudah default UTF-8, jadi jika Anda memerlukan encoding yang berbeda, Anda perlu menyesuaikan kode di atas dalam panggilan untuk menyertakan encoding untuk reader(charset)atau bufferedReader(charset).

Peringatan: Anda mungkin melihat contoh yang lebih pendek:

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

Tapi ini tidak menutup aliran . Pastikan Anda memeriksa dokumentasi API untuk semua fungsi IO yang Anda gunakan untuk memastikan mana yang dekat dan yang tidak. Biasanya jika mereka memasukkan kata use(seperti useLines()atau use()) mu menutup aliran setelahnya. Pengecualian adalah yang File.readText()berbeda dari Reader.readText()yang pertama tidak membiarkan apapun terbuka dan yang terakhir memang membutuhkan penutupan eksplisit.

Lihat juga: Fungsi ekstensi terkait Kotlin IO

Jayson Minard
sumber
1
Saya pikir "readText" akan menjadi nama yang lebih baik daripada "useText" untuk fungsi ekstensi yang Anda usulkan. Ketika saya membaca "useText", saya mengharapkan fungsi seperti useatau useLinesyang menjalankan fungsi blok pada apa yang sedang "digunakan". misalnya inputStream.useText { text -> ... }Di sisi lain, ketika saya membaca "readText" Saya berharap fungsi yang mengembalikan teks: val inputAsString = inputStream.readText().
mfulton26
Benar, tapi readText sudah memiliki arti yang salah, jadi ingin menandakannya lebih seperti usefungsi dalam hal itu. setidaknya dalam konteks Tanya Jawab ini. mungkin kata kerja baru dapat ditemukan ...
Jayson Minard
3
@ mfulton26 Saya menggunakan readTextAndClose()contoh ini untuk menghindari konflik dengan readText()pola tidak menutup, dan dengan usepola yang menginginkan lambda, karena saya tidak mencoba memperkenalkan fungsi stdlib baru, saya tidak ingin melakukan lebih dari sekadar menjelaskan tentang penggunaan ekstensi untuk menghemat tenaga kerja di masa depan.
Jayson Minard
@JaysonMinard mengapa Anda tidak menandai ini sebagai jawaban? itu bagus meskipun :-)
piotrek1543
2

Contoh yang membaca konten InputStream ke 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)
}

Untuk Referensi - Baca File Kotlin

Mallikarjun M
sumber
1
Contoh Anda mengandung kekurangan: 1) Untuk jalur lintas platform, Anda harus menggunakan Paths.get()metode. 2) Untuk streaming - fitur coba-sumber daya (Dalam kotlin: .use {})
Evgeny Lebedev