CodeGym /Courses /JAVA 25 SELF /BufferedReader, BufferedWriter: buffering, advantages

BufferedReader, BufferedWriter: buffering, advantages

JAVA 25 SELF
Level 36 , Lesson 0
Available

1. Introduction

Let’s recall how FileReader and FileWriter work. Every time you call their read() or write() method, there’s a call to the file system — that is, the computer actually hits the disk to read or write a single character. If the file is large and you read or write one character at a time, this turns into a marathon of thousands or even millions of disk accesses. And the disk, as we know, is not a programmer’s fastest friend.

You can think of it like this: you need to pour water from a bucket into a bottle, and you’re doing it with a teaspoon. Technically — it works, but it’s painfully slow. It’s much smarter to use a ladle or a mug. It’s the same with files: reading or writing one character at a time is like hauling water with a spoon.

Here’s what it looks like:

// Reading a file character by character (with a "teaspoon"!)
try (FileReader reader = new FileReader("big.txt")) {
    int c;
    while ((c = reader.read()) != -1) {
        // process the character (e.g., just count it)
    }
}

If the file is large, you’ll notice the program runs very slowly.

Buffering: what it is and why you need it

A buffer is a special memory area (usually an array) where data is loaded or written not one character at a time but in large chunks (for example, 8 KB or more). Simply put, a buffer is a piece of memory — something like an intermediate bucket — where data is read from or written to not by single characters, but in larger portions.

Here’s how it works: on read, the program accesses the disk once, loads a whole block of data into the buffer, and then serves it to you character by character from memory. As soon as the block is exhausted, the next one is loaded. On write — the same logic: data is first accumulated in the buffer, and then sent to the disk in one big chunk (or immediately if you call flush()).

Why is this faster? Because disks are slow, especially if you poke them for tiny operations. But if you access them less often and take more at once, everything runs noticeably faster. In short: buffering reduces the number of disk accesses and speeds up your program.

2. BufferedReader and BufferedWriter: syntax and examples

In Java, two classes are used to buffer reading and writing of text files:

  • BufferedReader — for reading text files.
  • BufferedWriter — for writing text files.

They work on top of regular Reader/Writer (for example, FileReader/FileWriter), adding buffering.

Reading a file line by line with BufferedReader

The most common scenario is to read a file by lines. The readLine() method returns a string up to the newline character ("\n" or "\r\n").

import java.io.*;

public class BufferedReaderExample {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line); // Print the line to the console
            }
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
        }
    }
}

What’s happening here?

We create a FileReader, which can read a file character by character, and wrap it in a BufferedReader. The buffered reader takes data not one character at a time but in large chunks, stores it in memory, and hands it to us line by line via readLine(). As a result, you just write a while loop, get lines one by one, and don’t worry about how big the file is: reading will still be fast and efficient.

Writing a file with BufferedWriter

Writing lines to a file can be efficient too:

import java.io.*;

public class BufferedWriterExample {
    public static void main(String[] args) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
            writer.write("Hello, world!");
            writer.newLine(); // New line (depends on the OS)
            writer.write("This is the second line.");
        } catch (IOException e) {
            System.out.println("Error writing file: " + e.getMessage());
        }
    }
}

What’s happening here?

First we create a FileWriter, and then wrap it in a BufferedWriter. When you call write() or newLine(), data does not go straight to disk. It’s accumulated in a buffer — a special memory layer. Only when the buffer fills up, or when you close the stream (or explicitly call flush()), the accumulated text is written to the file in one go. This approach significantly speeds up writing and reduces disk accesses.

What does this look like in a single application?

Suppose we’re writing a simple “diary” program that saves entries to a file and prints them to the screen.

import java.io.*;
import java.util.Scanner;

public class DiaryApp {
    public static void main(String[] args) {
        String fileName = "diary.txt";
        Scanner scanner = new Scanner(System.in);

        // Writing a new entry
        System.out.print("Enter a new diary entry: ");
        String entry = scanner.nextLine();

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName, true))) {
            writer.write(entry);
            writer.newLine();
            System.out.println("Entry saved!");
        } catch (IOException e) {
            System.out.println("Error while writing: " + e.getMessage());
        }

        // Reading all entries
        System.out.println("\nYour diary:");
        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("Error while reading: " + e.getMessage());
        }
    }
}

We take the file name "diary.txt" and prompt the user to enter a new entry. To save it, we use a FileWriter in append mode (passing true), so old notes aren’t erased — each new line is neatly added to the end of the file. The BufferedWriter wrapper makes writing fast and efficient: data is first accumulated in memory and then sent to disk in one chunk.

After that, we open the same file for reading via BufferedReader. It grabs the contents in large blocks and hands you lines one by one. In the loop, the program simply prints them to the console, and as a result you see your entire “diary” from start to finish.

3. Advantages of BufferedReader and BufferedWriter

Significant speed-up

When you read or write a file using buffered streams, the program makes tens, and sometimes hundreds, fewer disk accesses. This is especially noticeable on large files.

Convenient methods

  • BufferedReader.readLine() — lets you read a file line by line, which is very convenient for processing text files (e.g., logs, CSV, configuration files).
  • BufferedWriter.newLine() — adds a newline, correctly inserting the right character depending on the OS.

Ease of use

  • The classes are easy to combine with other streams (for example, you can wrap an InputStreamReader inside a BufferedReader to read files with different encodings).
  • They automatically close all resources when used with try-with-resources.

Flexibility

  • You can explicitly set the buffer size if the default (usually 8 KB) is too small or too large:
BufferedReader reader = new BufferedReader(new FileReader("big.txt"), 16384); // 16 KB buffer

4. When to use BufferedReader and BufferedWriter

Use them when:

  • You work with text files (logs, CSV, large text data).
  • You need to read or write a file line by line.
  • You need to speed up work with large files where performance matters.
  • You need to process a data stream from a network or another source that supports Reader/Writer.

Do not use them when:

  • Working with binary files (e.g., images, archives, video) — for that use InputStream/OutputStream.
  • The file is very small and is read/written once in full — here the gain from buffering will be minimal (but it won’t hurt either).

5. Useful details

Combining with specific encodings

If you need to read/write files in a specific encoding (for example, "UTF-8", "Windows-1251"), use InputStreamReader/OutputStreamWriter together with buffered streams:

BufferedReader reader = new BufferedReader(
    new InputStreamReader(new FileInputStream("input.txt"), "UTF-8")
);

BufferedWriter writer = new BufferedWriter(
    new OutputStreamWriter(new FileOutputStream("output.txt"), "UTF-8")
);

Explicitly flushing the buffer

Sometimes it’s important to ensure the data actually hits the disk (for example, if you’re writing a log or a receipt). Call writer.flush() for that. Usually this is not necessary, because closing the stream automatically flushes the buffer.

Buffer size

By default, the buffer is about 8 KB. You can set a different size if you know it will improve performance (for example, when processing huge files).

Comparison: FileReader/FileWriter vs. BufferedReader/BufferedWriter

Class Speed on large files Line-by-line reading convenience Line-by-line writing convenience Encoding flexibility
FileReader/FileWriter Slow No (character-by-character only) No (character-by-character only) Default only
BufferedReader/Writer Fast Yes (readLine()) Yes (newLine()) Yes (via InputStreamReader/OutputStreamWriter)

6. Common mistakes when working with BufferedReader and BufferedWriter

Mistake #1: Forgot to close the stream. If you don’t use try-with-resources or don’t call close(), the file may remain locked and the data may not be written to disk. Always use try-with-resources!

Mistake #2: Mixing up text and binary files. Trying to open a binary file (".jpg", ".zip") via BufferedReader will lead to garbled characters and likely errors. For binary files, use InputStream/OutputStream.

Mistake #3: Not using buffering for large amounts of data. If you read or write character by character, the program will be slow. Always use buffering for large files.

Mistake #4: Not calling flush() when necessary. If you need data to appear on disk immediately (for example, for logging), call writer.flush(). But in most cases closing the stream will do it for you.

Mistake #5: Ignoring encoding. If you open a file with the wrong encoding, text may be displayed incorrectly (letters turn into “?”, ideographs, etc.). Always explicitly specify the required encoding if it differs from the system one (for example, "UTF-8").

1
Task
JAVA 25 SELF, level 36, lesson 0
Locked
Extracting the First Thought from an Ancient Scroll 📜
Extracting the First Thought from an Ancient Scroll 📜
1
Task
JAVA 25 SELF, level 36, lesson 0
Locked
Recording the great truth into the annals of history ✍️
Recording the great truth into the annals of history ✍️
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION