Skip to Content
Course content

93: Buffered Streams and Readers

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've been looking at some old logs from a legacy server I'm migrating, and I need to pull out a few specific entries. Normally, I'd just use a text editor, but the file is massive—hundreds of megabytes—so I'm going to write a quick Java utility to parse it. Let's start with the most basic way I know how to read a text file: FileReader.

Reading one character at a time

My first instinct is just to open the file and loop through it. Here is how I'd start:


FileReader reader = new FileReader("server_logs.txt");
int data;
while ((data = reader.read()) != -1) {
    System.out.print((char) data);
}
reader.close();


Now, this works. If you run it, the text appears on the screen. But here's the problem: every single time reader.read() is called, Java is asking the operating system to go to the hard drive, grab one single character, and bring it back. Doing that a million times for a large log file is incredibly inefficient. It's like driving to the grocery store every time you need a single grain of rice for your dinner. It's a waste of gas and time.

Adding a buffer to the mix

I want to reduce those expensive system calls. Instead of grabbing one character, I want Java to grab a big chunk of the file and hold it in memory. That's where BufferedReader comes in. It doesn't replace FileReader; it wraps around it. I call this the "Decorator" pattern, though you don't need to worry about the formal name right now—just think of it as a layer of insulation.

Let's adjust the code:


FileReader fr = new FileReader("server_logs.txt");
BufferedReader br = new BufferedReader(fr);

int data;
while ((data = br.read()) != -1) {
    System.out.print((char) data);
}
br.close();


To the naked eye, the output is the same. But under the hood, BufferedReader has created an internal array (a buffer). The first time I call read(), it actually reads a large block (usually 8KB) from the disk and stores it. The next few thousand calls to read() are just pulling from that fast memory array, not the slow disk. It's significantly faster.

The real reason I use BufferedReader

While the performance boost is great, I'm still reading character by character, which is a nightmare for log files. Logs are organized by lines. If I want to find a specific error message, I don't want a stream of characters; I want a whole line of text.

This is where BufferedReader really shines because it gives us the readLine() method. Let's rewrite this to actually search for "ERROR" entries in my log file:


try (BufferedReader br = new BufferedReader(new FileReader("server_logs.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        if (line.contains("ERROR")) {
            System.out.println("Found issue: " + line);
        }
    }
} catch (IOException e) {
    e.printStackTrace();
}


Notice a couple of changes here. First, I used a try-with-resources block. I'm tired of manually calling .close() and worrying about leaks, so let the JVM handle it. Second, readLine() handles the heavy lifting of looking for newline characters (\n or \r\n) and returning the text as a complete String. This is infinitely more readable and maintainable than trying to manually build a string from individual characters.

What about binary data?

You might be wondering if this only works for text. It doesn't. If I were dealing with a binary file—like an image or a compiled .class file—I wouldn't use a Reader (which is for characters). I'd use a Stream. The logic remains exactly the same, though: FileInputStream is the "slow" way, and BufferedInputStream is the "fast" way. Whenever you're doing I/O in Java, if you aren't using a buffered version, you're probably leaving performance on the table.




📋 Practical Task

Build a Log-to-Summary Filter

You have a text file named system_activity.log. The file contains thousands of lines of logs, each starting with a severity level: INFO, WARN, or ERROR.

Write a Java program that uses a BufferedReader to read this file and perform the following tasks:

  • Count the total number of ERROR lines.
  • Count the total number of WARN lines.
  • Write only the ERROR lines to a new file called critical_errors.txt using a BufferedWriter.

Requirements:
1. Use try-with-resources to ensure all readers and writers are closed.
2. Use readLine() to process the file line-by-line.
3. Print the final counts of Errors and Warnings to the console.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.