The export that took four minutes
The canteen now has a year of orders: about 2 lakh lines. Riya's export writes them straight to a FileOutputStream, a few bytes at a time. It takes minutes. Worse, one night the last few hundred orders are simply missing from the file.
Sir, why is writing a file so slow? My laptop has an SSD!
The disk isn't the slow part. Every little write is a request to the operating system, and you're making two lakh of them. Put a buffer in between: collect 8 KB in memory, then send it in one go.
And the missing orders at the end?
Those were still sitting in a buffer when your program stopped. A buffer must be flushed, and close() does that. Let's see both.
What is buffered I/O?
Buffered I/O places an in-memory array (a buffer) between a program and an underlying stream, so that many small read or write operations are served from or collected in memory and transferred to the underlying stream in large blocks, reducing costly system calls. In Java, BufferedReader and BufferedWriter wrap any Reader or Writer (default buffer 8,192 characters), and BufferedInputStream and BufferedOutputStream wrap byte streams. BufferedReader.readLine() returns the next line without its terminator, or null at end of stream. Output buffers are written when full, on flush(), or on close().
Don't run to the post office with every letter. Fill a bag, then go once. But remember to actually take the bag before you leave.
Words you'll meet in this lesson
null at the end.Plain vs buffered
| Plain file streams | Buffered wrappers | |
|---|---|---|
| Read a line | No such method | readLine() |
| Write a line break | write("\n") | newLine() (platform separator) |
| OS calls for many small writes | FileOutputStream: one per call. (FileWriter has a small byte buffer inside its encoder, but every call still goes through it.) | One per full buffer |
| Data waits in memory? | Little | Yes, until flush/close |
| Create the modern way | — | Files.newBufferedReader/Writer(path) |
Exam tip: “Buffered streams improve performance by reducing the number of I/O operations. readLine() returns null at end of file. Always close() (or flush()) a buffered writer, or buffered data may be lost.” Also name the pattern: wrapping streams is the decorator design pattern.
The buffered family
// classic: wrap a file stream in a buffer
BufferedReader br = new BufferedReader(new FileReader("in.txt", StandardCharsets.UTF_8));
BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt", StandardCharsets.UTF_8));
// for bytes
InputStream in = new BufferedInputStream(new FileInputStream("photo.jpg"));
// modern shortcut (UTF-8, already buffered)
BufferedReader r = Files.newBufferedReader(Path.of("in.txt"));
BufferedWriter w = Files.newBufferedWriter(Path.of("out.txt"));BufferedReader
Reads text in blocks; readLine() and lines() for line-by-line processing.
BufferedWriter
Collects text; newLine() writes the platform line separator.
Buffered byte streams
BufferedInputStream, BufferedOutputStream for images, PDFs, binary data.
Files helpers
Files.newBufferedReader/Writer: buffered and UTF-8 in one call.
The data that wasn't there
import java.io.*;
import java.nio.file.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedWriter w = new BufferedWriter(new FileWriter("invoice.txt"));
w.write("INV-501 Rs 2360");
System.out.println("on disk now: " + Files.size(Path.of("invoice.txt")) + " bytes");
w.close();
System.out.println("after close: " + Files.size(Path.of("invoice.txt")) + " bytes");
}
}Unclosed buffered writers lose data. Nothing is wrong with the code until the very end: the text sits in memory and the file stays empty. If the program crashes or exits first, it's gone. Use try-with-resources every time, and call flush() if another program must see the data before you close.
Inside the readLine() loop
Wrap the reader
A decoder reader on the file, wrapped by a BufferedReader with an emptytry (BufferedReader br = Files.newBufferedReader(Path.of("orders.txt"))) {char[8192].First readLine(): fill
The buffer is empty, so the reader asks the file for up to 8,192 characters in one call.String line; while ((line = br.readLine()) != null) {Cut out one line
It scans forward fromposto the next\n(or\r\n), creates a String from those characters, and movespospast the line break.Next calls: from memory
Following lines come straight from the array until it runs out; then one more big read refills it.End
When the file has no more data,readLine()returnsnull, the loop ends, and try-with-resources closes the reader and the file.
Buffers on the way out and on the way in
Left: writing with and without a buffer. Right: a BufferedReader on the heap, filling its array once and serving lines from it.
- A plain
FileOutputStreampasses everywritestraight to the operating system. - Each call is a system call: the program switches to the OS kernel and back. Doing that thousands of times for tiny pieces is slow.
- A
BufferedWriterkeeps an array (8,192 characters) in memory.writejust copies into it: no OS call. - More writes, still in memory. This is extremely fast.
- When the buffer is full, or when you call
flush(), the whole chunk goes down in one call. close()flushes whatever is left, then closes the file. Forget it and the last part of your data never reaches the disk.
new BufferedReader(new FileReader("orders.txt")): two objects. The BufferedReader holds a reference to the FileReader and an emptychar[8192].- The first
readLine()asks the FileReader for up to 8,192 characters at once. This small file fits completely. readLine()finds the first line break in the array, returns the characters before it as a new String, and movesposforward.- The second line comes from the same array: no disk access at all.
- Same for the third. One read from the file served three lines.
- At the end of the data,
readLine()returnsnull. That's the loop condition:while ((line = br.readLine()) != null).
Buffered I/O in real systems
Practically every file and network stream in production Java code is buffered.
Watch it with an example: a bank's nightly statement file
A bank writes 2 lakh transaction lines every night. Without a buffer, each line is a separate trip to the disk; with one, lines are sent in big blocks.
- Each night the bank writes one line per transaction: date, account, amount. About 2 lakh lines, 5 MB.
- Written straight to a
FileOutputStreamwith no buffer, every line is its own write call to the OS. Buffered, lines collect in an 8 KB array, so one call carries about 300 lines. - The unbuffered job keeps switching into the OS kernel; the buffered one is mostly copying characters in memory.
- The file contents are identical. Only the number of trips differs: 2 lakh vs about 600. (Counts are approximate; the exact number depends on line lengths.)
- That's why production code always wraps file streams in buffers, or uses
Files.newBufferedWriter, which does it for you.
1. Log processing
Support teams scan gigabytes of logs for errors, reading line by line so memory stays tiny.
2. Bulk exports
Statements, GST returns and data dumps with lakhs of lines are written through BufferedWriter.
3. Network sockets
Reading an HTTP response or a chat protocol line by line uses a BufferedReader on the socket's InputStream.
4. Competitive programming and input
Fast solutions read huge inputs with BufferedReader instead of Scanner, which does more work per token.
Counts and sizes are illustrative; exact numbers depend on line lengths and buffer sizes.
Write a menu, read it line by line
import java.io.*;
import java.nio.file.*;
public class Main {
public static void main(String[] args) throws IOException {
try (BufferedWriter w = Files.newBufferedWriter(Path.of("menu.txt"))) {
w.write("Masala Dosa,80");
w.newLine();
w.write("Idli Sambar,50");
w.newLine();
w.write("Filter Coffee,25");
w.newLine();
}
try (BufferedReader r = Files.newBufferedReader(Path.of("menu.txt"))) {
String line;
while ((line = r.readLine()) != null) {
String[] f = line.split(",");
System.out.printf("%-14s Rs %3s%n", f[0], f[1]);
}
}
}
}Files.newBufferedWriter(Path.of("menu.txt"))Buffered, UTF-8, creates or replaces the file.w.newLine()Writes the line separator for the current OS.(line = r.readLine()) != nullRead and test in one step; stops at end of file.printf("%-14s Rs %3s%n", …)Aligned columns: name left-aligned in 14 characters, price right-aligned in 3.
Reading orders with readLine()
Recorded from the real JVM. Watch line on the stack take each value, and total grow; on the heap, the BufferedReader holds the FileReader it wraps.
Worked examples: real problems, solved and traced in memory
Three real uses: proving how many OS calls a buffer saves, writing a report with BufferedWriter, and filtering a large log line by line. Watch the stack: only one line is held at a time.
Count the trips to the OS
Problem statement
Prove that buffering reduces system calls. Write 20 bytes one at a time, first straight to a FileOutputStream, then through a BufferedOutputStream with a tiny 8-byte buffer (so the trace stays short; the real default is 8,192). Count the calls that reach the file stream with a small counting wrapper.
20 single-byte writes, buffer of 8 bytes
unbuffered: 20 calls to the file buffered: 3 calls to the file
Approach
- A
Countingclass extendsFilterOutputStreamand counts every write that reaches it. - Put it directly under the loop, then under a BufferedOutputStream.
- Compare the counts. Each call into a FileOutputStream is one OS call.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Write a salary-slip summary with BufferedWriter
Problem statement
Payroll must write one summary line per employee to payroll.txt, then read the file back with BufferedReader to confirm the line count and the total payout.
Asha 52000, Ravi 47500, Meena 61000, Imran 39000
4 slips, total payout Rs 199500
Approach
Files.newBufferedWritergives a BufferedWriter with UTF-8.- Write each line with
write+newLine(). - Read back with
Files.newBufferedReaderandreadLine().
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Pull the errors out of a big log
Problem statement
A server log has thousands of lines. Support needs only the ERROR lines, saved to errors.txt. Read line by line (never the whole file at once), write matching lines, and report counts.
a generated log of 12 lines, every 4th an ERROR (real logs have millions)
read 12 lines, saved 3 errors first error: ERROR payment timeout #4
Approach
- Generate a test log with a BufferedWriter.
- Open a BufferedReader on the log and a BufferedWriter on errors.txt in one try.
- For each line containing "ERROR", write it out. Only one line is in memory at a time.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Check yourself
Quick revision
Lesson 13.3 in one screen
- A buffer collects data in memory so the OS is called once per block, not once per character.
- Wrap:
new BufferedReader(reader),new BufferedWriter(writer), or useFiles.newBufferedReader/Writer. - Loop:
while ((line = br.readLine()) != null); lines come without the line break. newLine()writes the platform separator.- Buffered output is only on disk after
flush()orclose(); use try-with-resources. - Closing the outer wrapper closes the inner streams too.
- Process big files line by line: memory stays constant.
Interview and viva questions
Why is BufferedReader faster than FileReader?
Every FileReader.read() call goes through the charset decoder and its locking for a single character. BufferedReader pulls 8,192 characters at once into its own array and serves later reads with plain array access. With raw byte streams such as FileInputStream, where each read() is a system call, the difference is even larger. BufferedReader also adds readLine().
What is the difference between flush() and close()?
flush() writes out buffered data but keeps the stream open for more writes. close() flushes and then releases the stream and its underlying resources; the stream can't be used afterwards.
BufferedReader vs Scanner?
BufferedReader reads lines of text quickly with a large buffer and does no parsing. Scanner parses tokens (ints, words) with regular expressions, which is convenient but slower, and has a smaller buffer. For large inputs, read lines with BufferedReader and parse them yourself.
What design pattern do Java I/O wrappers use?
The decorator pattern: a BufferedReader is a Reader and wraps a Reader, adding buffering without changing the interface. You can stack decorators, e.g. a BufferedReader over an InputStreamReader over a FileInputStream.
When must you call flush() explicitly?
When someone else must see the data before you close: e.g. sending a message over a socket and waiting for a reply, or writing a progress file that another program monitors. Otherwise, closing (via try-with-resources) is enough.
Practice problems
Read with BufferedReader.readLine(), write with BufferedWriter, and never forget to flush or close. Each program creates its own files. The last three are business tasks.
Read every line
Write three city names to cities.txt, then print each line with its number using BufferedReader.
Pune, Nagpur, Nashik
1: Pune 2: Nagpur 3: Nashik
Show solution and explanation
import java.io.*;
import java.nio.file.*;
public class Main {
public static void main(String[] args) throws IOException {
Files.writeString(Path.of("cities.txt"), "Pune\nNagpur\nNashik\n");
try (BufferedReader br = new BufferedReader(new FileReader("cities.txt"))) {
String line;
int n = 1;
while ((line = br.readLine()) != null) {
System.out.println(n++ + ": " + line);
}
}
}
}How it works: the assignment inside the condition reads a line and checks it for null in one go. This loop shape is worth memorising.
Write lines with newLine()
Write three lines with a BufferedWriter and newLine(), then print how many lines the file has.
(none)
3 tasks saved
Show solution and explanation
import java.io.*;
import java.nio.file.*;
public class Main {
public static void main(String[] args) throws IOException {
try (BufferedWriter bw = new BufferedWriter(new FileWriter("todo.txt"))) {
bw.write("pay electricity bill");
bw.newLine();
bw.write("book train ticket");
bw.newLine();
bw.write("call bank");
bw.newLine();
}
System.out.println(Files.readAllLines(Path.of("todo.txt")).size() + " tasks saved");
}
}How it works: newLine() writes the platform's line separator (\n on Linux and macOS, \r\n on Windows).
flush() before reading
Write hello with a BufferedWriter but don't close it. Print the file size, then call flush() and print it again.
(none)
before flush: 0 bytes after flush: 5 bytes
Show solution and explanation
import java.io.*;
import java.nio.file.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("draft.txt"));
bw.write("hello");
System.out.println("before flush: " + Files.size(Path.of("draft.txt")) + " bytes");
bw.flush();
System.out.println("after flush: " + Files.size(Path.of("draft.txt")) + " bytes");
bw.close();
}
}How it works: until the buffer is flushed, the data exists only in your program's memory. If the program crashed before flush() or close(), those 5 bytes would be lost.
Longest line
Find the longest line in a file of product names and print it with its length.
4 product names
Basmati Rice 5kg (16 chars)
Show solution and explanation
import java.io.*;
import java.nio.file.*;
import java.util.List;
public class Main {
public static void main(String[] args) throws IOException {
Files.write(Path.of("products.txt"), List.of("Tea", "Basmati Rice 5kg", "Sugar", "Sunflower Oil 1L"));
String longest = "";
try (BufferedReader br = Files.newBufferedReader(Path.of("products.txt"))) {
String line;
while ((line = br.readLine()) != null) {
if (line.length() > longest.length()) longest = line;
}
}
System.out.println(longest + " (" + longest.length() + " chars)");
}
}How it works: only the current line and the best so far are kept, so this works for files of any size.
Count lines with lines()
Use BufferedReader.lines() to count non-empty lines in a file that has some blank lines.
5 lines, 2 blank
non-empty lines: 3
Show solution and explanation
import java.io.*;
import java.nio.file.*;
public class Main {
public static void main(String[] args) throws IOException {
Files.writeString(Path.of("notes.txt"), "buy milk\n\ncall Ravi\n\nsubmit GST\n");
try (BufferedReader br = Files.newBufferedReader(Path.of("notes.txt"))) {
long filled = br.lines().filter(l -> !l.isBlank()).count();
System.out.println("non-empty lines: " + filled);
}
}
}How it works: lines() turns the reader into a lazy Stream of lines (Lesson 14.3), reading as it goes.
Copy a text file line by line
Copy menu.txt to menu-copy.txt using a BufferedReader and BufferedWriter, converting each line to upper case.
3 dishes
[MASALA DOSA, IDLI SAMBAR, FILTER COFFEE]
Show solution and explanation
import java.io.*;
import java.nio.file.*;
import java.util.List;
public class Main {
public static void main(String[] args) throws IOException {
Files.write(Path.of("menu.txt"), List.of("masala dosa", "idli sambar", "filter coffee"));
try (BufferedReader in = Files.newBufferedReader(Path.of("menu.txt"));
BufferedWriter out = Files.newBufferedWriter(Path.of("menu-copy.txt"))) {
String line;
while ((line = in.readLine()) != null) {
out.write(line.toUpperCase());
out.newLine();
}
}
System.out.println(Files.readAllLines(Path.of("menu-copy.txt")));
}
}How it works: resources in one try are closed in reverse order: the writer first (flushing it), then the reader.
Bank statement summary
statement.csv has date,type,amount lines with type CR or DR. Read it with BufferedReader and print total credits, total debits and the closing balance from an opening balance of 10,000.
5 transactions
credits 28000, debits 10150, closing 27850
Show solution and explanation
import java.io.*;
import java.nio.file.*;
import java.util.List;
public class Main {
public static void main(String[] args) throws IOException {
Files.write(Path.of("statement.csv"), List.of(
"01-10,CR,25000", "03-10,DR,1200", "07-10,DR,8500", "15-10,CR,3000", "20-10,DR,450"));
long cr = 0, dr = 0;
try (BufferedReader br = Files.newBufferedReader(Path.of("statement.csv"))) {
String line;
while ((line = br.readLine()) != null) {
String[] f = line.split(",");
long amt = Long.parseLong(f[2]);
if (f[1].equals("CR")) cr += amt; else dr += amt;
}
}
System.out.println("credits " + cr + ", debits " + dr + ", closing " + (10000 + cr - dr));
}
}How it works: streaming through the statement keeps memory use constant whether it has 5 lines or 5 million.
Split a big order file by city
orders.csv has id,city,amount. Write Pune orders to pune.csv and all others to others.csv, then print the line counts.
6 orders
pune.csv: 3, others.csv: 3
Show solution and explanation
import java.io.*;
import java.nio.file.*;
import java.util.List;
public class Main {
public static void main(String[] args) throws IOException {
Files.write(Path.of("orders.csv"), List.of("1,Pune,450", "2,Mumbai,900", "3,Pune,120", "4,Nagpur,300", "5,Pune,75", "6,Mumbai,640"));
try (BufferedReader in = Files.newBufferedReader(Path.of("orders.csv"));
BufferedWriter pune = Files.newBufferedWriter(Path.of("pune.csv"));
BufferedWriter rest = Files.newBufferedWriter(Path.of("others.csv"))) {
String line;
while ((line = in.readLine()) != null) {
BufferedWriter target = line.split(",")[1].equals("Pune") ? pune : rest;
target.write(line);
target.newLine();
}
}
System.out.println("pune.csv: " + Files.readAllLines(Path.of("pune.csv")).size()
+ ", others.csv: " + Files.readAllLines(Path.of("others.csv")).size());
}
}How it works: one pass over the input feeds two buffered outputs. Each writer is flushed and closed when the try ends.
Resume-safe batch: flush every 100 lines
A job writes 250 receipt lines. To limit loss on a crash, it calls flush() every 100 lines. Count the flushes, including the final close.
250 receipts
250 receipts, 3 flushes
Show solution and explanation
import java.io.*;
import java.nio.file.*;
public class Main {
public static void main(String[] args) throws IOException {
int flushes = 0;
try (BufferedWriter w = Files.newBufferedWriter(Path.of("receipts.txt"))) {
for (int i = 1; i <= 250; i++) {
w.write("RCPT-" + i);
w.newLine();
if (i % 100 == 0) {
w.flush();
flushes++;
}
}
}
flushes++;
System.out.println(Files.readAllLines(Path.of("receipts.txt")).size() + " receipts, " + flushes + " flushes");
}
}How it works: flushing too often removes the benefit of buffering; never flushing risks losing everything since the start. Periodic flushes (or checkpoints) are the usual compromise in batch jobs.
Real-world scenario questions (asked in company interviews)
These come up whenever a role involves batch jobs, reports or integrations.
The last 300 rows are missing
An export writes 50,000 rows with a BufferedWriter. The file always ends a few hundred rows short, and the last row is cut in half. The code calls System.exit(0) right after the loop. Explain.
The interviewer is checking: buffers and flushing.
Show a strong answer
- The writer is never closed, so the final partly-filled buffer (up to 8 KB) never reaches the file;
System.exitdoesn't flush it. - Wrap the writer in try-with-resources (or close it in finally) before exiting.
Avoid: adding flush() after every row; that removes the benefit of buffering.
A 10-minute report that should take seconds
Profiling shows a report spends 95% of its time inside FileOutputStream.write, called once per character. How do you fix it with minimal code change?
The interviewer is checking: recognising unbuffered I/O.
Show a strong answer
- Each call is a system call. Wrap the stream:
new BufferedOutputStream(new FileOutputStream(f)), or useFiles.newBufferedWriterfor text. - Optionally use a larger buffer (64 KB) for very large outputs, and write whole strings rather than single characters.
Avoid: rewriting the report in multiple threads first; the bottleneck is the call pattern.
The partner server never answers
A client sends a request line to a payment partner over a socket with a BufferedWriter, then waits for the reply with readLine(). It hangs forever, though the partner says it never received anything. Why?
The interviewer is checking: when explicit flush is needed.
Show a strong answer
- The request is still in the client's buffer; it's never sent because the writer wasn't flushed. Both sides wait for each other.
- Call
writer.flush()after writing a complete request, before reading the reply. - Also set read timeouts so a missing reply can't hang the client forever.
Avoid: closing the writer to force sending; closing the socket stream closes the connection.
Files from Windows have an extra character
A Linux job reads a CSV uploaded from a Windows machine with Files.readString, splits the text on "\n", then parses the last column of each row. It fails with NumberFormatException: For input string: "450\r". Why, and how should it read the file?
The interviewer is checking: line terminators and defensive parsing.
Show a strong answer
- Windows ends lines with
\r\n. Splitting on"\n"leaves the\rat the end of every row. readLine()(andlines()) treat\n,\rand\r\nall as line ends and strip them, so use those instead of splitting by hand.- Read with readLine (or
lines()),trim()fields before parsing, and watch for a UTF-8 BOM at the start of files from Excel.
Avoid: assuming every file uses your OS's line endings.