SwadhyAI Notes

All Java notes

Java I/O Streams

›Short versionOpen if you only have a minute.
  • A stream is a one-way sequence of data and the handle that carries it.
  • Byte streams move 8-bit values. Character streams move Unicode characters.
  • A charset maps between those two forms. Name UTF-8 when the file is text.
  • A buffer holds a chunk in memory. Output stays there until flush or close.
  • Close the outermost wrapper. try-with-resources does that close on the way out.
  • A BufferedWriter can accept a short write and report a failure only when that buffer is flushed.

A file of text and a file of pixels both arrive as bytes on disk. The program has to decide whether those bytes should stay bytes, or become characters. That decision is the whole shape of java.io.

A is the object you read from or write to. A stays on the bytes. A works in characters, after a has done the conversion. A sits in front of either kind so a loop of small reads or writes does not become a loop of small operating-system calls.

Bytes and characters

InputStream and OutputStream are the byte pair. Reader and Writer are the character pair. FileInputStream and FileOutputStream belong to the first pair. BufferedReader and BufferedWriter belong to the second.

Text that lives in a file is still bytes. The bridge is InputStreamReader on the way in and OutputStreamWriter on the way out. Each one takes the byte stream and a charset. UTF-8 is the charset to pass for a file you expect to open on another machine.

Casting a byte to char skips that bridge. Values above 127 become the wrong character, and a character that occupies more than one byte is split. The reader is what reassembles those bytes.

ReadNotes.java
try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(new FileInputStream("notes.txt"), StandardCharsets.UTF_8))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

The innermost object is the file. The reader decodes UTF-8. The buffer lets readLine() serve characters that were pulled from the file in a larger chunk. One owns the outer reader. Closing that reader closes the reader under it, and that close closes the file stream.

What the buffer is holding

A buffered reader fills its array from the source, then answers read and readLine from memory until the array runs out. A buffered writer does the reverse. write copies characters into the array and returns. The wrapped writer hears about them when the array fills, when you call flush(), or when close() flushes as its first step.

That delay is why a BufferedWriter can hide a crash until flush. A short line fits in the default buffer of 8192 characters. write succeeds. If the disk is full, the pipe is broken, or the underlying stream rejects the bytes, the IOException is thrown from the later flush or close, not from the line that looked finished. If the process is killed before that flush, there is no exception at all. The characters existed only in the buffer, and the file still shows the previous flush.

A write large enough to fill the array flushes on its own, in the middle of write. The quiet case is the small one. Call flush() when another reader must see the bytes before the writer closes. Otherwise let close do it, and make sure close actually runs.

Bytes stay bytes

Copying a photo, a zip, or a PDF uses the byte pair and a byte[] you fill yourself. There is no charset in that path. The count returned by read is how many slots in the array hold new data. The rest of the array is leftover from earlier, and it must not be written.

CopyBytes.java
byte[] chunk = new byte[8192];
try (FileInputStream in = new FileInputStream("photo.jpg");
     FileOutputStream out = new FileOutputStream("photo-copy.jpg")) {
    int count;
    while ((count = in.read(chunk)) != -1) {
        out.write(chunk, 0, count);
    }
}

FileOutputStream has no character buffer of its own. Its flush() does not push one, and it does not force the operating system to commit pages to disk. The flush that changes behavior is the one on BufferedWriter or BufferedOutputStream, because that is where program memory is still holding the data.

You called write with four characters on a BufferedWriter, then opened the file before close. The file is empty. Where are the characters, and when would a full disk tell you?

They are in the writer's buffer. A four-character write does not fill 8192 characters, so the wrapped writer has not been asked to encode them yet. A full disk throws when the buffer is pushed: an explicit flush, a later write that fills the array, or the close that try-with-resources runs. Until one of those, the write has already returned.