[Java] [缓冲区] 不要在同一个输入流使用多个Scanner
代码(Java)
预期代码可满足下面的功能:输入三个字符串,每输入一个字符串后,立即打印它。
代码0(从文件输入)
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Scanner;
public final class Main {
public static void main(String[] args) throws FileNotFoundException {
InputStream f = new FileInputStream("Input.txt");
Scanner in_0 = new Scanner(f);
String s0 = in_0.next();
System.out.println("Your 0 String is " + s0);
Scanner in_1 = new Scanner(f);
String s1 = in_1.next();
System.out.println("Your 1 String is " + s1);
Scanner in_2 = new Scanner(f);
String s2 = in_2.next();
System.out.println("Your 2 String is " + s2);
System.out.println("END.");
}
}
Input.txt
Output:
Your 0 String is A
Exception in thread “main” java.util.NoSuchElementException
at java.base/java.util.Scanner.throwFor(Scanner.java:937)
at java.base/java.util.Scanner.next(Scanner.java:1478)
at Main.main(Main.java:18)
注:第18行为 String s1 = in_1.next();
代码1(与代码1相同,只不过改为从命令行输入)
import java.io.FileNotFoundException;
import java.util.Scanner;
public final class Main {
public static void main(String[] args) throws FileNotFoundException {
Scanner in_0 = new Scanner(System.in);
String s0 = in_0.next();
System.out.println("Your 0 String is " + s0);
Scanner in_1 = new Scanner(System.in);
String s1 = in_1.next();
System.out.println("Your 1 String is " + s1);
Scanner in_2 = new Scanner(System.in);
String s2 = in_2.next();
System.out.println("Your 2 String is " + s2);
System.out.println("END.");
}
}
Input & Output
倘若改为每输入一个字母就按一次Enter
观察
可见,只有当从命令行输入,且每次只输入一个字符串时,程序才运行正常。
若非如此,
从命令行输入的程序都将会在in_0
之后,一直处于等待输入的状态;
从文件输入的程序将会出现NoSuchElementException
,认为A
之后就没有字符串而到达EOF
了,即使我们的文件中确确实实有5(当然大于2)个字符串。
解释
上面出现的问题可归因于:我们在程序中使用了多个Scanner
。
Scanner如何作用
Scanner
内部利用正则表达式,将缓冲区的输入内容进行分析,帮助我们快速得到需要的输入数据。下面是这一过程的实现步骤:
- 我们在命令行,或通过文件,输入内容;
- 内容进入
标准输入缓冲区
; -
Scanner
将标准输入缓冲区
的内容拿到自己的缓冲区
内,并进行分析; -
Scanner
根据我们的需求,从自己的缓冲区
再次拿走相应内容,返回给我们; - 我们得到相应内容。
造成上述问题的关键是3、4步
。下面,我们利用这个5步模版解释具体的代码。
对于代码0(从文件输入)
[注意
6
中的“等也没有用”]
- 我们通过文件输入内容
A B C D E
; - 内容
A B C D E
进入标准输入缓冲区
; -
in_0
将标准输入缓冲区
的内容拿到自己的缓冲区
内,并进行分析;此后,标准输入缓冲区
内容为空
,in_0缓冲区
内容为A B C D E
。当然,in_1 in_2缓冲区
也为空
; -
in_0
根据我们的需求in_0.next(),获取字符串
,从自己的缓冲区
再次拿走相应内容A
,返回给我们;此后,标准输入缓冲区
内容为空
,in_0缓冲区
内容变为B C D E
。当然,in_1 in_2缓冲区
也为空
; - 我们得到相应内容,
屏幕上打印Your 0 String is A
; -
in_1
想要重复上面的in_0
的操作,但是标准输入缓冲区
已被in_0
读完了,A
以外的内容现在在in_0自己的缓冲区
内。in_1
看到的只是空空如也的标准输入缓冲区
,等也没有用,因为文件内容已经全部到了标准输入缓冲区
(当然后来又被in_0
取走了),文件已经到底(EOF
),不可能再有内容; - 就这样,无辜的
in_1
只能说,NoSuchElementException
。
对于代码1(从命令行输入,一次性输入)
道理与上面类似。
对于代码1(从命令行输入,每输入完,都按Enter
)
[注意
6
中的等待是有用的]
- 我们在命令行输入内容
A
; - 内容进入
标准输入缓冲区
; -
in_0
将标准输入缓冲区
的内容拿到自己的缓冲区
内,并进行分析;此后,标准输入缓冲区
内容为空
,in_0缓冲区
内容为A
。当然,in_1 in_2缓冲区
也为空
; -
in_0
根据我们的需求,从自己的缓冲区
再次拿走相应内容,返回给我们;此后,标准输入缓冲区
内容为空
,in_0缓冲区
内容变为空
。当然,in_1 in_2缓冲区
也为空
; - 我们得到相应内容,
屏幕上打印Your 0 String is A
。 -
in_1
想要重复上面的in_0
的操作,但是标准输入缓冲区
已被in_0
读完了。不过,由于这是命令行,所以我们随时可以输入内容,不存在上面的EOF
,输入没有到底。所以,in_1
开始等待我们输入新的内容; - 我们在命令行输入内容
B
; - 内容进入
标准输入缓冲区
; -
in_1
将标准输入缓冲区
的内容拿到自己的缓冲区
内,并进行分析;此后,标准输入缓冲区
内容为空
,in_1缓冲区
内容为B
。当然,in_0 in_2缓冲区
也为空
; - 我们得到相应内容,
屏幕上打印Your 1 String is B
。 - 如此进行下去……
总结
导致上面问题的关键点是:
- 输入内容会全部进入
输入缓冲区
,Scanner
会把输入缓冲区
的内容全部拿入自己的缓冲区
,然后才开始分析我们的需求; - 不同
Scanner
的缓冲区是不同的。
总地来说,就是不要使用多个Scanner
。同样的道理扩展开来,就是Do not create multiple buffered wrappers on a single byte or character stream。
更正
import java.io.FileNotFoundException;
import java.util.Scanner;
public final class Main {
public static void main(String[] args) throws FileNotFoundException {
Scanner in = new Scanner(System.in);
String s0 = in.next();
System.out.println("Your 0 String is " + s0);
String s1 = in.next();
System.out.println("Your 1 String is " + s1);
String s2 = in.next();
System.out.println("Your 2 String is " + s2);
System.out.println("END.");
}
}
参考
上一篇: NIO(一)——Buffer
下一篇: Linux中主函数的参数和缓冲区的理解