Sunday, September 09, 2012

How to read multiple lines from the standard input stream in Java

I was facing this problem, of reading lines of text from the console input, as I was under some intense time-pressure, while I was taking an online test. I was convinced that it must be something simple like:



String line = System.in.readln();


Not such luck. This seemingly simple task in Java ain't that simple. Googling for this revealed (in multiple places) something along these lines:


import java.io;
try {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
return in.readLine();
} catch (IOException e) {
System.err.println(e);
}


which seems straightforward, until you try running this sequence repeatedly, perhaps in a loop.

If you feed this a multi-line input, pasted in the console, like so:

one
two
three

then you'd notice that the first gets read fine, then the subsequent reads block, without catching the lines:

two
three

Bad times. There's a fatal flaw in the code above, cited on so many sites, from one of which I copied it too. The readers hooked up to System.in don't get closed, and if that sequence of code is run in a loop we'd end up with multiple readers for the same single standard input stream. Now one could argue that an exception should be thrown in such case, where multiple readers are trying to slurp data from the same source. Alas it doesn't, and instead the behaviour is truly puzzling.

My faulty code had this function called repeatedly:


public static String readline() {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
return in.readLine();
} catch (IOException e) {
return null;
}
}


The fix consisted in doing this instead:


static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static String readline() {
try {
return in.readLine();
} catch (IOException e) {
return null;
}
}


The static variable gets initialized only once, and now calling readLine() repeatedly works fine.

Alternatively in Java 1.6 one could also use:


public static String readline() {
return System.console().readLine();
}


with the caveat that this approach using console() doesn't work by default when running in Eclipse.