library
intermediateJava I/O Streams: Byte vs Character
Distinguish InputStream/OutputStream (bytes) from Reader/Writer (characters) and know when to use each.
Two hierarchies. Bytes for binary, readers and writers for text.
Byte streams = a raw pipe (any liquid flows through). Character streams = a water filter (converts raw water to drinkable, accounting for impurities/encoding).
Key Concepts
1
InputStream / OutputStream raw bytes
Reader / Writer characters, with an encoding2
// binary
try (var in = new FileInputStream("photo.jpg")) { ... }3
// text
try (var r = Files.newBufferedReader(Path.of("notes.txt"))) {
String line = r.readLine();
}4
Using the wrong one corrupts data. A byte stream reading text has no idea about encoding, so a
multi-byte character splits across reads. A reader over a JPEG mangles it trying to decode bytes as
characters.
5
Always state the encoding.
new FileReader("f.txt") // platform default -- a bug
new FileReader("f.txt", StandardCharsets.UTF_8)
Files.readString(path) // UTF-8 by default6
The platform default differs between your laptop and the server, which is how a file that reads correctly in
development arrives as question marks in production. Java 18 made UTF-8 the default, which helps on new
versions only.
7
Always buffer. An unbuffered read is one system call per byte:
8
new FileInputStream(f) // a syscall per read
new BufferedInputStream(new FileInputStream(f)) // 8 KB at a time9
The difference on a large file is orders of magnitude.
10
The bridge classes convert between the two:
new InputStreamReader(inputStream, UTF_8)
new OutputStreamWriter(outputStream, UTF_8)11
Which is what you need for a socket or an HTTP body, where the source is bytes and the content is text.
12
Always close, with try-with-resources. An unclosed writer may never flush, so the file ends up truncated
with no error anywhere.
13
And for most jobs, skip streams entirely.
String s = Files.readString(path);
List<String> lines = Files.readAllLines(path);
Files.writeString(path, s);14
NIO.2 handles opening, buffering, decoding and closing. Reach for streams when the file is too large to hold
in memory, or when you are wrapping something that is not a file.
15
What the interviewer is probing.1. "When do you use a Reader rather than an InputStream?" Probing: the binary and text split.
Stalls: "They are interchangeable." Moves up: Readers decode bytes into characters using a
charset; a byte stream over text splits multi-byte characters, and a Reader over a JPEG corrupts it.
16
2. "What is wrong with new FileReader(path)?" Probing: the default charset. Stalls:
"Nothing." Moves up: it used the platform default charset, so a file that read correctly in
development arrived as question marks on the server; always state the charset.
17
3. "Why wrap a stream in a buffered one?" Probing: syscall cost. Stalls: "It is faster."
Moves up: an unbuffered read is a system call per byte; buffering reads 8KB at a time, which is
orders of magnitude on a large file.
18
4. "What happens if you never close a writer?" Probing: flushing. Stalls: "The file is
fine." Moves up: buffered content may never be flushed, so the file is silently truncated — which
is what try-with-resources prevents.