在编写软件构造实验三的过程中,碰到了一个问题,就是我在一个类Main主函数中定义了一个Scanner流去读取控制台的输入,然后再在这个类其他方法中再重新定义一个Scanner流去读入数据,读完后回到主函数再用主函数的Scanner流去读,此时,发现,这与我顺序输入的结果不相同。下面给出例子:
代码(Java)
预期代码可满足下面的功能:输入三个字符串,每输入一个字符串后,立即打印它。
代码0(从文件输入)
Input.txt
代码1(与代码1相同,只不过改为从命令行输入)
Input & Output
倘若改为每输入一个字母就按一次Enter
观察
可见,只有当从命令行输入,且每次只输入一个字符串时,程序才运行正常。
若非如此,
从命令行输入的程序都将会在in_0之后,一直处于等待输入的状态;
从文件输入的程序将会出现NoSuchElementException,认为A之后就没有字符串而到达EOF了,即使我们的文件中确确实实有5(当然大于2)个字符串。
解释
上面出现的问题可归因于:我们在程序中使用了多个Scanner。
Scanner如何作用
Scanner内部利用正则表达式,将缓冲区的输入内容进行分析,帮助我们快速得到需要的输入数据。下面是这一过程的实现步骤:
我们在命令行,或通过文件,输入内容;
内容进入标准输入缓冲区;
Scanner 将标准输入缓冲区的内容拿到自己的缓冲区内,并进行分析;
Scanner根据我们的需求,从自己的缓冲区再次拿走相应内容,返回给我们;
我们得到相应内容。
造成上述问题的关键是3、4步。下面,我们利用这个5步模版解释具体的代码。
对于代码0(从文件输入)
我们通过文件输入内容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(从命令行输入,一次性输入)
道理与上面类似。
我们在命令行输入内容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。
更正
参考
Do not create multiple buffered wrappers on a single byte or character stream
StackOverFlow-How to use multiple Scanner objects on System.in?