Why does Java's Scanner make double calls to read()
and how can I manage custom input for game simulations?
I'm trying to simulate Codingame's way of managing game input, which typically looks like this:
Scanner in = new Scanner(System.in);
int width = in.nextInt(); // columns in the game grid
int height = in.nextInt(); // rows in the game grid
// Game loop
while (true) {
int entityCount = in.nextInt();
// Process more input
}
To achieve this, I created a custom InputStream
. The problem is that Scanner
seems to make double calls to the read()
method for a single call to nextInt()
.
Issue
- If I return
-1
twice in succession during theread()
calls, theScanner
treats the stream as closed, causing it to throw aNoSuchElementException
. - To handle this, I found that returning a dummy space character (
' '
) after-1
prevents the stream from being marked as closed. - Additionally, I want
Scanner
to wait for the next call tonextInt()
before reading more data from the stream.
Here's my current solution:
private class GameStream extends InputStream {
private String buffer = "";
private int position = 0;
private int playerNum;
private boolean spaceFound = false;
public GameStream(int playerNum) {
this.playerNum = playerNum;
setBuffer(getTurnData(playerNum));
}
public GameStream setBuffer(String buffer) {
this.buffer = buffer;
position = 0;
return this;
}
@Override
public int read() throws IOException {
// Stop reading until the next call to nextInt()
if (spaceFound) {
spaceFound = false;
return -1;
}
if (position < buffer.length()) {
int c = buffer.charAt(position++);
if (c == ' ') {
spaceFound = true;
}
return c;
} else {
players.get(playerNum).getHisData = true;
// Prevent Scanner from closing the stream
setBuffer(" ");
return -1;
}
}
}
Questions
- Why does
Scanner
callread()
twice for a singlenextInt()
call? - What's the proper way to make
Scanner
wait for new data when callingnextInt()
without resorting to dummy characters (' '
)?
simple code showing the problem
class GameStream extends InputStream {
private String buffer = "";
private int position = 0;
public GameStream(String buffer) {
setBuffer(buffer);
}
public GameStream setBuffer(String buffer) {
this.buffer = buffer;
position = 0;
return this;
}
@Override
public int read() throws IOException {
if (position < buffer.length()) {
int c = buffer.charAt(position++);
return c;
} else {
System.out.println("player read all data");
//setBuffer("without_dummy_space_should_fail ");
return -1;
}
}
}
usage
GameStream gs = new GameStream("first turn finish_");
Scanner in = new Scanner(gs);
System.out.println(in.next() + " " + in.next() + " " + in.next());
gs.setBuffer("next turn_");
System.out.println(in.next()+ " " + in.next());
out put
player read all data before
player read all data before
first turn finish_without_dummy_space_should_fail
player read all data before
player read all data before
next turn_without_dummy_space_should_fail
I hope I explained my issue clearly. Any insights would be greatly appreciated!
Why does Java's Scanner make double calls to read()
and how can I manage custom input for game simulations?
I'm trying to simulate Codingame's way of managing game input, which typically looks like this:
Scanner in = new Scanner(System.in);
int width = in.nextInt(); // columns in the game grid
int height = in.nextInt(); // rows in the game grid
// Game loop
while (true) {
int entityCount = in.nextInt();
// Process more input
}
To achieve this, I created a custom InputStream
. The problem is that Scanner
seems to make double calls to the read()
method for a single call to nextInt()
.
Issue
- If I return
-1
twice in succession during theread()
calls, theScanner
treats the stream as closed, causing it to throw aNoSuchElementException
. - To handle this, I found that returning a dummy space character (
' '
) after-1
prevents the stream from being marked as closed. - Additionally, I want
Scanner
to wait for the next call tonextInt()
before reading more data from the stream.
Here's my current solution:
private class GameStream extends InputStream {
private String buffer = "";
private int position = 0;
private int playerNum;
private boolean spaceFound = false;
public GameStream(int playerNum) {
this.playerNum = playerNum;
setBuffer(getTurnData(playerNum));
}
public GameStream setBuffer(String buffer) {
this.buffer = buffer;
position = 0;
return this;
}
@Override
public int read() throws IOException {
// Stop reading until the next call to nextInt()
if (spaceFound) {
spaceFound = false;
return -1;
}
if (position < buffer.length()) {
int c = buffer.charAt(position++);
if (c == ' ') {
spaceFound = true;
}
return c;
} else {
players.get(playerNum).getHisData = true;
// Prevent Scanner from closing the stream
setBuffer(" ");
return -1;
}
}
}
Questions
- Why does
Scanner
callread()
twice for a singlenextInt()
call? - What's the proper way to make
Scanner
wait for new data when callingnextInt()
without resorting to dummy characters (' '
)?
simple code showing the problem
class GameStream extends InputStream {
private String buffer = "";
private int position = 0;
public GameStream(String buffer) {
setBuffer(buffer);
}
public GameStream setBuffer(String buffer) {
this.buffer = buffer;
position = 0;
return this;
}
@Override
public int read() throws IOException {
if (position < buffer.length()) {
int c = buffer.charAt(position++);
return c;
} else {
System.out.println("player read all data");
//setBuffer("without_dummy_space_should_fail ");
return -1;
}
}
}
usage
GameStream gs = new GameStream("first turn finish_");
Scanner in = new Scanner(gs);
System.out.println(in.next() + " " + in.next() + " " + in.next());
gs.setBuffer("next turn_");
System.out.println(in.next()+ " " + in.next());
out put
player read all data before
player read all data before
first turn finish_without_dummy_space_should_fail
player read all data before
player read all data before
next turn_without_dummy_space_should_fail
I hope I explained my issue clearly. Any insights would be greatly appreciated!
Share Improve this question edited Feb 2 at 2:57 Ahmed Mazher asked Feb 2 at 0:16 Ahmed MazherAhmed Mazher 3333 silver badges7 bronze badges 3 |2 Answers
Reset to default 2
- Why does Scanner call read() twice for a single nextInt() call?
Because it obviously has to. Imagine we have a user typing at a keyboard. They type '1', and that's all they type.
You call .nextInt()
on this stream, and the scanner dutifully calls read()
which returns the unicode value for '1'.
Is that the number? 1 is a number. But wait! Next, the user types 5! Oh dear, they actually meant to type 15.
Hence why scanner has to keep reading. nextInt()
does not mean 'read a single digit'. It means 'read a single number' and it can consist of multiple digits. Hence, scanner must keep reading; it either [A] finds a delimiter (e.g. after the '1', the user types a space or enter), or [B] end of stream.
Either way, once scanner sees that it can return an actual int value.
- What's the proper way to make Scanner wait for new data when calling nextInt() without resorting to dummy characters (' ')?
By not responding at all. Let's go back to that user behind the keyboard.
We have your java program that has:
Scanner s = new Scanner(System.in);
// `.useDelimiter` is not called, so the delimiter is
// the default one, which is `\\s+`, i.e. 1 or more whitespace.
s.nextInt();
The nextInt()
will immediately call .read()
on the thing scanner wrapped (so, System.in
), and this blocks. That read()
call does not return at all, it freezes your java program. Only when you type an actual '1' does read()
unfreeze to return 49 (the unicode value for the character '1'). scanner reads it, and immediately calls read()
because it cannot return '1' (maybe you're going to type a 7 next, after all), and then that read()
call blocks. You then type 7, so 7 goes in, and scanner bookkeeps 'we've got 17 so far', and then finally you hit space, and scanner can now return and is not going to call read()
again until some code of yours calls one of the nextX()
methods of scanner.
At no point does anything go into an endless loop. I have the feeling you think this is happening inside .nextInt()
:
// THIS IS NOT HOW IT WORKS!
int out = 0;
while (true) {
// 'in' = System.in here.
int v = in.read();
// If no user input is available, keep waiting.
if (v == -1) continue;
// Did we hit the delimiter? Then return the number.
if (Character.isWhitespace(v)) return out;
// 'stuff' the new digit into our output and go back
// to asking for more keystrokes.
int digit = v - '0';
out = (out * 10) + digit;
}
If it actually worked like that, your computer's fans would spin up and the CPU load goes t 100%. while (true) { continue; }
makes things become very very hot and sluggish, you don't wanna do that. Instead, in.read();
freezes the thread, until you actually type something.
So that's how you make scanner wait for more input. By not returning at all inside your implementation of read()
, until you actually have stuff to return.
But how do I wait?
It's not exactly trivial to update your code to do this. You'll need to involve threads. Most likely you want your GameStream
to be a separate thread, which brings in the considerable mental load of knowing how to 'safely' manage them. Basically, if 2 threads interact with the same field, your code is broken in 'evil coin' ways (the JVM flips an evil coin and chooses whether the write to the field done in thread A is actually propagated to the reads done in thread B. The coin is evil in that it isn't random; it does that all day today and then tomorrow all of a sudden it will not. The solution is to tell the JVM not to flip the coin at all; you do this by establishing Happens Before relationships; this is all rather tricky!).
It's solvable of course, just, you need to do some reading. such as Java Concurrency in Practice. Or if you want to leave that for later, make sure all interaction with that buffer class is protected with synchronized
or, better yet, with something appropriate from the java.util.concurrent
package.
If you just want to keep sending new data to a Scanner
after its creation, consider using a PipedReader
/PipedWriter
pair.
var writer = new PipedWriter();
var reader = new PipedReader(os);
writer.write("first turn finish_ "); // remember the delimiter at the end!
Scanner in = new Scanner(reader);
System.out.println(in.next() + " " + in.next() + " " + in.next());
writer.write("next turn_ ");
System.out.println(in.next()+ " " + in.next());
You should include delimiters (strings that match the delimiter()
pattern, e.g. a space) at the end of the inputs, so that the scanner knows that that is a complete token, or else it will keep trying to read more data and cause a deadlock (the thread is blocked while the scanner tries to read, and you cannot write
more data unless you are on a different thread). Of course, the same thing happens if you try to call next
when all the currently written data has been read already.
java.util.Scanner
. You might improve the question by editing to include what you learned from debugging. – Old Dog Programmer Commented Feb 2 at 0:37ByteArrayInputStream
, or aStringReader
- both can be used withScanner
... butScanner
itself has a constructor for reading from aString
(e.g.Scanner in = new Scanner("first turn finish_");
) – user85421 Commented Feb 2 at 10:35