ใน Kotlin ฉันจะอ่านเนื้อหาทั้งหมดของ InputStream เป็น String ได้อย่างไร


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 สำหรับฟังก์ชัน IO ทั้งหมดที่คุณใช้เพื่อให้แน่ใจว่าฟังก์ชันใดใกล้เคียงและไม่ปิด โดยปกติถ้าพวกเขามีคำว่าuse(เช่นuseLines()หรือuse()) คุณปิดสตรีมหลังจากนั้น ข้อยกเว้นคือFile.readText()แตกต่างจากReader.readText()ที่ก่อนหน้านี้ไม่ได้เปิดอะไรทิ้งไว้และอย่างหลังต้องการการปิดอย่างชัดเจน

ดูเพิ่มเติม: ฟังก์ชั่นส่วนขยายที่เกี่ยวข้องกับ Kotlin IO


1
ฉันคิดว่า "readText" น่าจะเป็นชื่อที่ดีกว่า "useText" สำหรับฟังก์ชันส่วนขยายที่คุณเสนอ เมื่อฉันอ่าน "useText" ฉันคาดหวังว่าฟังก์ชันเช่นuseหรือuseLinesที่เรียกใช้ฟังก์ชันบล็อกกับสิ่งที่ "ใช้" อยู่ เช่นinputStream.useText { text -> ... }บนมืออื่น ๆ เมื่อฉันอ่าน "readText" val inputAsString = inputStream.readText()ผมคาดหวังว่าฟังก์ชั่นซึ่งจะส่งกลับข้อความ:
mfulton26

จริง แต่ readText มีความหมายที่ผิดอยู่แล้วดังนั้นจึงต้องการแสดงความหมายว่ามันเหมือนกับuseฟังก์ชันในเรื่องนั้นมากกว่า อย่างน้อยก็ในบริบทของคำถาม & คำตอบนี้ อาจพบคำกริยาใหม่ ...
Jayson Minard

3
@ mfulton26 ฉันreadTextAndClose()ใช้ตัวอย่างนี้เพื่อหลีกเลี่ยงความขัดแย้งกับreadText()รูปแบบของการไม่ปิดและด้วยuseรูปแบบที่ต้องการแลมบ์ดาเนื่องจากฉันไม่ได้พยายามแนะนำฟังก์ชัน stdlib ใหม่ฉันไม่ต้องการทำอะไรมากไปกว่าการใช้ประเด็น ส่วนขยายเพื่อประหยัดแรงงานในอนาคต
Jayson Minard

@JaysonMinard ทำไมคุณไม่ทำเครื่องหมายนี้สำหรับคำตอบ? มันเยี่ยมมากแม้ว่า :-)
piotrek1543

2

ตัวอย่างที่อ่านเนื้อหาของ InputStream เป็น 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)
}

สำหรับการอ้างอิง - ไฟล์อ่าน Kotlin


1
ตัวอย่างของคุณมีข้อบกพร่อง: 1) สำหรับเส้นทางข้ามแพลตฟอร์มคุณควรใช้Paths.get()วิธีการ 2) สำหรับสตรีม - คุณลักษณะลองทรัพยากร (ใน kotlin: .use {})
Evgeny Lebedev

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

เพื่อปรับปรุงคำตอบโปรดให้คำบรรยายอธิบายวิธีการทำงาน
Kirby
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.