Denis Bazhenov Denis Bazhenov - 3 months ago 20
Java Question

java.util.Scanner.hasNextLine() returns true even if there is no next line available

Suppose following code:

String example = " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n" +
" 0: 00000000:04D2 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 15662 1 ffff8800baf1c780 100 0 0 10 0\n" +
" 1: 00000000:04D2 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 15662 1 ffff8800baf1c780 100 0 0 10 0";

Scanner scanner = new Scanner(example);
scanner.useRadix(16).useDelimiter("[\\s:]+");

while (scanner.hasNextLine()) {
scanner.nextLine(); // skip header on first iteration, which is not needed
int slot = scanner.nextInt();
System.out.println(slot);
}


I am basically reading the first integer on each line except the header. I'm expecting to see following output:

0
1


But exception thrown instead:

java.util.NoSuchElementException
at java.util.Scanner.throwFor(Scanner.java:862)
at java.util.Scanner.next(Scanner.java:1485)
at java.util.Scanner.nextInt(Scanner.java:2117)
at java.util.Scanner.nextInt(Scanner.java:2076)


The question is why
Scanner.hasNextLine()
returns
true
on the last line where clearly no newline available in the stream?



Answer

This happens at the end of your input. You parse the 1, which leaves you at the space following it. Then your loop condition is still true (there's a line available), so you read the line, which leaves you at the very end of your string. Then, with no checks, you try to read an int. So it fails. You need to check whether there's an int available, because on the last line, there isn't.

Here's a version checking to see if there's an int available, and showing what you're skipping, which should make things clearer (live copy):

String example = "  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode\n" +
    "   0: 00000000:04D2 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 15662 1 ffff8800baf1c780 100 0 0 10 0\n" +
    "   1: 00000000:04D2 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 15662 1 ffff8800baf1c780 100 0 0 10 0";

Scanner scanner = new Scanner(example);
scanner.useRadix(16).useDelimiter("[\\s:]+");
Set<Integer> result = new HashSet<>();
scanner.next();

while (scanner.hasNextLine()) {
    String skipped = scanner.nextLine();
    System.out.println("Skipped: " + skipped); // skip header on first iteration and also skips remainder of previous line after we read the int
    if (scanner.hasNextInt()) {
        int slot = scanner.nextInt();
        System.out.println(slot);
    } else {
        System.out.println("No int avaialble");
    }
}

System.out.println("Done");

There I've added a System.out.println for what's skipped, but really what you want to do is step through this in a debugger.

Here's what it ouputs:

Skipped:   local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
0
Skipped: : 00000000:04D2 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 15662 1 ffff8800baf1c780 100 0 0 10 0
1
Skipped: : 00000000:04D2 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 15662 1 ffff8800baf1c780 100 0 0 10 0
No int avaialble
Done

Now, the above is a bit complicated because you're reading part of the line (the int at the beginning) on one loop but reading the rest of the line (the part after the int) at the beginning of the next loop. Ideally you'd clean those up in the same loop (live copy):

String example = "  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode\n" +
    "   0: 00000000:04D2 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 15662 1 ffff8800baf1c780 100 0 0 10 0\n" +
    "   1: 00000000:04D2 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 15662 1 ffff8800baf1c780 100 0 0 10 0";

Scanner scanner = new Scanner(example);
scanner.useRadix(16).useDelimiter("[\\s:]+");
Set<Integer> result = new HashSet<>();
scanner.next();

if (scanner.hasNextLine()) {
    // Skip the headers
    scanner.nextLine();
}
// Process records
while (scanner.hasNextLine()) {
    // The first int
    if (scanner.hasNextInt()) {
        int slot = scanner.nextInt();
        System.out.println(slot);

        // Read anything else you want from that line, until
        // you have only the newline left (or any trailing characters
        // you don't want to process and then the newline)
    }

    // Clear the newline
    scanner.nextLine();
}

System.out.println("Done");

Output:

0
1
Done