How exactly does bufferedReader() work in Kotlin?

The readText function is defined as an extension on Reader:

public fun Reader.readText(): String {
    val buffer = StringWriter()
    copyTo(buffer)
    return buffer.toString()
}

An InputStream isn't a Reader, so you have to transform it into some Reader:

public inline fun InputStream.reader(charset: Charset = Charsets.UTF_8): InputStreamReader = 
    InputStreamReader(this, charset)

You can use the reader as a buffered reader with the alternative bufferedReader function:

public inline fun InputStream.bufferedReader(charset: Charset = Charsets.UTF_8): BufferedReader = 
    reader(charset).buffered()

Reader and also BufferedReader are part of the Java standard library and the buffered version is described like this:

Reads text from a character-input stream, buffering characters so as to provide for the efficient reading of characters, arrays, and lines.

In general, each read request made of a Reader causes a corresponding read request to be made of the underlying character or byte stream. It is therefore advisable to wrap a BufferedReader around any Reader whose read() operations may be costly, such as FileReaders and InputStreamReaders...

It basically wraps a Reader and adds support for reading single lines etc.


val file = context.assets.open("myfile.json").bufferedReader().readText()

Here is the code that do the same thing as above line.

val inputStream = context.assets.open("myfile.json")
val reader = inputStream.bufferedReader()
val file = reader.readText()

Assume here is the content of myfile.json

{
    "os": "Android",
    "version": "KitKat",
    "codeName": 4.4
}

Let's go step by step

Step 1: The first line

val inputStream = context.assets.open("myfile.json")

This will be return an InputStream object, which reads one byte or number of bytes from the json file. If you print the json file content in byte format on the screen, it will really hard for us (as programmers) to read.

Step 2: The second line

val reader = inputStream.bufferedReader()

This will create a BufferedReader object, which read a character or a number of characters from the json file, but they have another useful method named readLine(), this method reads a line of text. A line is considered to be terminated by any one of a line feed ('\n'), a carriage return ('\r'), or a carriage return followed immediately by a linefeed.

Let's modify the current code.

val inputStream = context.assets.open("myfile.json")
val reader = inputStream.bufferedReader()

// Read line by line from reader until reach the end.
var line = reader.readLine()
while(line != null) {
    Log.i("TAG", line)
    line = reader.readLine()
}

Output:

I/TAG: {
I/TAG:     "os": "Android",
I/TAG:     "version": "KitKat",
I/TAG:     "codeName": 4.4
I/TAG: }

As we can see, they print 5 lines from the json file. But in some cases, we want to print all the json file as a String, that why we move to next step.

Step 3: The third line

val file = reader.readText()

This will reads the buffer reader completely as a String. You can write your own to do the same like.

val inputStream = context.assets.open("myfile.json")
val reader = inputStream.bufferedReader()

val sb = StringBuffer()

var line = reader.readLine()
while(line != null) {
    Log.i("TAG", line)
    sb.append(line).append("\n")
    line = reader.readLine()
}

val file = sb.toString()

Log.i("TAG", file)

Output:

I/TAG: {
        "os": "Android",
        "version": "KitKat",
        "codeName": 4.4
}

This output is the same as reader.readText().

Conclusion: BufferReader wraps an InputStream (or sub-classes of InputStream) inside them, then provide methods to read character-by-character instead of byte-by-byte in InputStream. In addition, they provide readLine() method, buffer data.

InputStream (byte-by-byte) -> Reader (character-by-character)

InputStream (byte-by-byte) -> BufferReader (character-by-character, read line, buffer data).