Fast I/O in Java: BufferedReader vs Scanner and PrintWriter
Scanner is slow on big inputs; BufferedReader with StringTokenizer is about ten times faster. The Java fast I/O template for competitive coding, and flush().
- Course: Java study plan
- Module: I/O, files and time
- Kind: Lesson
- Reading time: 13 min
- Runtime: OpenJDK 18
What is the fastest way to read input in Java?
The fastest common way to read input in Java is a BufferedReader over System.in with a StringTokenizer, parsing each token with Integer.parseInt — about ten times faster than Scanner, which tokenises with regular expressions. Pair it with a buffered PrintWriter for output and call flush() at the end. For ten million numbers, a hand-written byte reader is faster still.
Lesson
A Scanner reading a million integers takes several seconds; a BufferedReader with a StringTokenizer does it in a fraction of one; a hand-rolled byte reader is faster still. System.out.println in a loop of a million lines is equally slow. None of this matters for a five-line program and all of it decides whether a correct solution passes a judge's time limit. This lesson explains why buffering matters, gives the standard fast-I/O template every competitive Java programmer carries, and covers PrintWriter, printf and the flush rules that catch people out.
Why unbuffered I/O is slow
Every read() on a raw FileInputStream or System.in can be a system call — a trip into the kernel that costs about a microsecond. Reading bytes one at a time is a million system calls for a megabyte. A BufferedInputStream/BufferedReader asks the kernel for 8 KB at once and serves the next 8 000 reads from memory. The same on output: BufferedWriter collects text and writes it in chunks. Buffering is not an optimisation for large files only; it is the difference between I/O that costs microseconds and I/O that costs nothing measurable.
Scanner adds a second cost: it tokenises with regular expressions and boxes each result (nextInt parses through a String), which is several hundred nanoseconds per token. Fine for a hundred numbers; catastrophic for a million.
The standard fast template
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(in.readLine());
int n = Integer.parseInt(st.nextToken());
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
for (int i = 0; i < n; i++) {
while (!st.hasMoreTokens()) st = new StringTokenizer(in.readLine()); // tokens may span lines
int x = Integer.parseInt(st.nextToken());
out.println(x * 2);
}
out.flush(); // NOTHING is printed until this line
}
}
The while (!st.hasMoreTokens()) refill is the detail people forget: input that says "n integers" may put them on one line or fifty, and a tokenizer that assumes one line per number reads null and throws. Wrap that logic in a next() helper and you have a reader that does not care about line structure — exactly how Scanner behaves, at a tenth of the cost.
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static StringTokenizer st;
static String next() throws IOException {
while (st == null || !st.hasMoreTokens()) st = new StringTokenizer(in.readLine());
return st.nextToken();
}
static int nextInt() throws IOException { return Integer.parseInt(next()); }
static long nextLong() throws IOException { return Long.parseLong(next()); }
in.readLine() returns null at end of input; a program that reads "until EOF" loops on while ((line = in.readLine()) != null).
Faster still: a byte reader
For the heaviest inputs (10⁷ numbers), even readLine + parseInt allocates a String per line. The competitive-programming answer reads raw bytes and parses digits by hand:
static DataInputStream din = new DataInputStream(new BufferedInputStream(System.in, 1 << 16));
static int readInt() throws IOException {
int ret = 0, b = din.read();
while (b <= ' ') b = din.read(); // skip whitespace
boolean neg = b == '-';
if (neg) b = din.read();
while (b >= '0' && b <= '9') { ret = ret * 10 + (b - '0'); b = din.read(); }
return neg ? -ret : ret;
}
Zero allocation per number. It is ugly, it is what the top of every Java leaderboard uses, and you should be able to write it from memory — while saying in an interview that in application code you would never need it.
Output: PrintWriter and flush
System.out is a PrintStream with autoflush on every println when attached to a console — a system call per line. Wrapping it in PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out))) (or new PrintWriter(System.out) — buffered by default, no autoflush) turns a million printlns into a few hundred writes. The price: you must flush() (or close()) at the end, or the output stays in the buffer and the program prints nothing. A judge that shows "no output" for a program that obviously computes the answer is almost always a missing flush.
Alternatively build the whole output in a StringBuilder and print it once: sb.append(x).append('\n') in the loop, System.out.print(sb) at the end. Same effect, one flush, and no IOExceptions to declare.
printf/String.format are convenient and slow (a format-string parse per call); in a hot loop prefer append. When you do format, pass Locale.ROOT or Locale.US for decimals — a judge running in a German locale prints 3,14 and fails your output.
Reading everything at once
For small-to-medium inputs the simplest fast approach is one read: String all = new String(System.in.readAllBytes(), UTF_8) then all.split("\\s+") or all.lines(). One system call, one allocation of the whole text, and every parsing tool of the strings module available. Watch the empty-input edge case (split on "" yields [""], not []).
Character-at-a-time when the format demands it
Some inputs are grids of characters, or expressions with no whitespace. in.read() on a BufferedReader returns one char as an int (−1 at end); in.readLine().toCharArray() gives a row of a grid. Scanner has no nextChar — next().charAt(0) is the workaround — another reason the reader wins.
Interview angle
- "Why is
Scannerslow?" Regex tokenising and per-token parsing through strings; aBufferedReader+StringTokenizeris ~10× faster. - "What does buffering actually save?" System calls: one 8 KB read serves thousands of small reads.
- "Why did my program print nothing?" Buffered output never flushed — call
flush()/close()on thePrintWriter. - "How do you read until end of input?"
readLine()returnsnullat EOF;Scanner.hasNext()returnsfalse. - "Fastest way to read ten million integers in Java?" A byte-level reader over
BufferedInputStream, parsing digits by hand.
Key takeaways
- Unbuffered I/O is a system call per byte; buffer everything, always.
- Template:
BufferedReader+StringTokenizerwith a refill loop,PrintWriterout,flush()at the end. - Byte-level parsing for the extreme cases;
readAllBytes+splitfor the simple ones. - Format with
Locale.ROOT; avoidprintfin hot loops;StringBuilderthen one print is fine. readLine()→nullat EOF;read()→ −1.
Common questions
Why is Scanner slow in Java?
Scanner tokenises its input with regular expressions and parses each token through a String, which costs several hundred nanoseconds per token. That is fine for a hundred numbers and far too slow for a million; BufferedReader plus StringTokenizer is about ten times faster.
Why does my Java program print nothing with PrintWriter?
A PrintWriter wrapped around System.out buffers its output and does not flush on println, so everything stays in memory unless you call flush() or close() before the program ends. A judge showing no output for a correct program is almost always a missing flush.
How do you read input until EOF in Java?
With a BufferedReader, loop on while ((line = in.readLine()) != null), because readLine() returns null at the end of input. With a Scanner, loop while hasNext() is true; a raw read() returns -1 at the end.
What does buffering save in Java I/O?
System calls. Every read() on an unbuffered stream can be a trip into the kernel costing about a microsecond; a BufferedReader or BufferedInputStream fetches 8 KB at once and serves the next thousands of reads from memory.
Why pass a Locale when formatting decimals in Java?
printf and String.format use the default locale, so a machine set to German prints 3.14 as 3,14. Pass Locale.ROOT or Locale.US so decimal output is identical wherever the program runs.
Exercises
The fast reader
Read an integer n followed by n long integers that may be spread over any number of lines, using a BufferedReader and a StringTokenizer with a refill loop — no Scanner. Write the answer through a PrintWriter and flush it: sum=<sum> min=<smallest> max=<largest>.
Example (three lines): 5, 3 9, -2 7 1 →
sum=18 min=-2 max=9A buffered report
Read an integer n and n records name score. Through a PrintWriter wrapped around a buffered writer on System.out, print one CSV line per record, name,score,grade, where the grade is A for 90 and above, B for 75–89, C for 50–74, else F; then average=<mean score, two decimals> (use Locale.ROOT). Flush at the end.
Example: 3 then ann 91, bob 75, cy 40 →
ann,91,A
bob,75,B
cy,40,F
average=68.67In this module: I/O, files and time
← Bytes and characters — streams, readers and encodings · Files and paths — java.nio.file →