一、问题背景
博主最近在LeetCode和牛客网做编程题目。在leetcode平台,做编程题目的时候只需要完成所给的类的方法,使用时,输入为方法输入的形式参数,输出为程序的返回值。而在牛客网上的题目,包括一些竞赛时的题目,形式为输入的是从控制台键入的几行数据,而输出是直接将结果打印到控制台。因此博主决定好好研究一下Java数据的输入方法,其中最原始的Java数据输入方法便是Scanner类及其方法。
二、Java的Scanner类
1.Scanner类的构造函数
Java的Scanner类有8种构造方法,其中最常用的构造方法便是Scanner(InputStream source)
,此构造方法是从形式参数中的输入流InputStream source
扫描而构造一个新的Scanner对象。
Scanner(InputStream source)
我们通常用类似Scanner scanner=new Scanner(System.in);
来实例化一个Scanner对象,示例代码如下:
import java.util.Scanner;
public class ScannerTest {
public static void main(String[] args){
//System.in代表标准输入,即键盘输入到控制台终端
//该行代码表示创建一个通过键盘输入到控制台终端的Scanner对象
Scanner scanner=new Scanner(System.in);
System.out.println("请输入Scanner将读取的文本:");
//让Scanner对象检测控制台终端是否有键盘输入。若没有键盘输入,那么Scanner对象会一直等待键盘输入,从而造成线程阻塞
if(scanner.hasNext()){
//若有键盘输入,则以字符串String形式把键盘输入打印到控制台终端
System.out.println("刚输入的文本是:"+scanner.next());
}
}
}
上述代码scanner.hasNext()
的注释中写到:若没有键盘输入,那么Scanner对象会一直等待键盘输入,从而造成线程阻塞
。那什么是线程阻塞呢?
线程阻塞(thread blocking)
是指在等待某个条件发生时(如某资源就绪或获得键盘输入),单个线程的执行被暂停从而导致线程被阻塞。例如,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后用户进程才能进行。Java传统的IO模型属于此种方式。
执行上述代码,我们在控制的终端输入Hello World
,结果控制台终端的输出确是Hello
。这是为什么呢?
这是因为Scanner对象默认将空白(空格、Tab空白、回车)作为多个输入项之间的分隔符,因此Hello World
便被拆解为Hello
和World
两个输入项,而我们只有一条scanner.next()
语句,故控制台终端仅输出第一个输入项Hello
。
2.Scanner类的hasNext()系列方法
在Java SE 1.6的API文档关于Scanner类的方法定义中,有以下几个常用的hasNext()系列方法:
Scanner类的hasNext()系列方法 | 方法详情 |
---|---|
public boolean hasNext() | 检测控制台终端缓存中是否有输入项,若无输入项则会一直等待键盘输入,从而可能造成线程阻塞;在等待键盘输入时若获得输入项,则将输入项存入控制台终端缓存,并返回boolean类型的true 。若控制台终端缓存中有输入项,则直接返回boolean类型的true 。同时控制台终端缓存中的输入项保持不变,并等待Scanner对象的next()系列方法取出。 |
public boolean hasNextDouble() | 检测控制台终端缓存中是否有输入项,若无输入项则会一直等待键盘输入,从而可能造成线程阻塞;在等待键盘输入时若获得输入项,则将输入项存入控制台终端缓存,再检测输入项是否为double浮点型,是则返回boolean类型的true ,否则返回false 。若控制台终端缓存中有输入项,依然检测输入项是否为double浮点型,是则返回boolean类型的true ,否则返回false 。同时控制台终端缓存中的输入项保持不变,并等待Scanner对象的next()系列方法取出。 |
public boolean hasNextInt() | 检测控制台终端缓存中是否有输入项,若无输入项则会一直等待键盘输入,从而可能造成线程阻塞;在等待键盘输入时若获得输入项,则将输入项存入控制台终端缓存,再检测输入项是否为int整型,是则返回boolean类型的true ,否则返回false 。若控制台终端缓存中有输入项,依然检测输入项是否为int整型,是则返回boolean类型的true ,否则返回false 。同时控制台终端缓存中的输入项保持不变,并等待Scanner对象的next()系列方法取出。 |
2.1 Scanner类的hasNext()系列方法实测代码1号
Scanner类的hasNext()系列方法实测代码1号如下:
import java.util.Scanner;
public class ScannerTest {
public static void main(String[] args){
//System.in代表标准输入,即键盘输入到控制台终端
//该行代码表示创建一个通过键盘输入到控制台终端的Scanner对象
Scanner scanner=new Scanner(System.in);
System.out.println("请输入Scanner将读取的文本:");
System.out.println(scanner.hasNext());
System.out.println(scanner.hasNextInt());
System.out.println(scanner.hasNextInt());
System.out.println(scanner.hasNextDouble());
System.out.println(scanner.hasNextDouble());
System.out.println("刚输入的文本是:"+scanner.next());
System.out.println("刚输入的文本是:"+scanner.next());
System.out.println("刚输入的文本是:"+scanner.next());
}
}
上述实测代码的输入、输出的测试结果如下:
由实测代码的输入、输出的测试结果可知:在Scanner类的hasNext()系列方法执行后,控制台终端缓存中的输入项保持不变,并等待Scanner对象的next()系列方法取出。
这个实测代码的输入、输出的测试结果是想告诉我们:虽然实测代码中有5条hasNext()系列方法,但在控制台终端的缓存中只会存在1份若干输入项ab cd 12 ef 3.4
,不会因为有5条hasNext()系列方法而有5份若干输入项ab cd 12 ef 3.4
。而且hasNext()系列方法在检测控制台终端缓存中是否有输入项时,也不会将输入项从控制台终端缓存中取出,而这也可由实测代码的输出结果5条的hasNext()系列方法都只检测了第一个字符串类型的输入项ab
证明。
总共5条的scanner.hasNext()
、scanner.hasNextInt()
和scanner.hasNextDouble()
在检测控制台终端缓存中的输入项时都只检测了第一个字符串类型的输入项ab
,所以scanner.hasNext()
输出true
,其余输出false
。
为什么hasNext()系列方法未检测
ab
后面的cd 12 ef 3.4
?
因为ab
和后面的cd 12 ef 3.4
之间有分隔符——空格符,hasNext()系列方法默认读取控制台终端及其缓存中第一个分隔符前面的输入项。
2.2 Scanner类的hasNext()系列方法实测代码2号
Scanner类的hasNext()系列方法实测代码2号如下:
import java.util.Scanner;
public class ScannerTest {
public static void main(String[] args){
//System.in代表标准输入,即键盘输入到控制台终端
//该行代码表示创建一个通过键盘输入到控制台终端的Scanner对象
Scanner scanner=new Scanner(System.in);
System.out.println("请输入Scanner将读取的文本:");
System.out.println(scanner.hasNextInt());
System.out.println("刚输入的文本是:"+scanner.next());
}
}
上述实测代码的输入、输出的测试结果如下:
由实测代码的输入、输出的测试结果可知:无论Scanner类的hasNext()系列方法在获得输入项后返回值是true
或false
,总会将输入项存入控制台终端缓存。
3.Scanner类的next()系列方法
Scanner类的next()系列方法 | 方法详情 |
---|---|
public String next() | 检测控制台终端及其缓存中是否有输入项。若没有输入项,则会一直等待键盘输入,从而可能造成线程阻塞;若有输入项则返回String字符串类型的输入项。同时因为被next()系列方法取出,该输入项将在缓存中消失。 |
public double nextDouble() | 检测控制台终端及其缓存中是否有输入项。若没有输入项,则会一直等待键盘输入直到获得输入项,从而可能造成线程阻塞;若有输入项再检测输入项是否为double浮点型,是则返回double浮点型的输入项,否则在程序运行时抛出异常InputMismatchException。同时因为被next()系列方法取出,该输入项将在缓存中消失。 |
public int nextInt() | 检测控制台终端及其缓存中是否有输入项。若没有输入项,则会一直等待键盘输入直到获得输入项,从而可能造成线程阻塞;若有输入项再检测输入项是否为int整型,是则返回int整型的输入项,否则在程序运行时抛出异常InputMismatchException。同时因为被next()系列方法取出,该输入项将在缓存中消失。 |
public String nextLine() | 检测控制台终端及其缓存中是否有输入项。若没有输入项,则会一直等待键盘输入,从而可能造成线程阻塞;若有输入项则在不以空格符(Space)和制表符(Tab)为分隔符,而仅以回车(Enter)为分隔符 的条件下,以一行为单位返回String字符串类型的输入项。同时因为被nextLine()方法取出,该行的输入项将在缓存中消失。 |
3.1 Scanner类的next()系列方法实测代码1号
Scanner类的next()系列方法实测代码1号如下:
import java.util.Scanner;
public class ScannerTest {
public static void main(String[] args){
//System.in代表标准输入,即键盘输入到控制台终端
//该行代码表示创建一个通过键盘输入到控制台终端的Scanner对象
Scanner scanner=new Scanner(System.in);
System.out.println("请输入Scanner将读取的文本:");
System.out.println("刚输入的文本是:"+scanner.nextDouble());
}
}
上述实测代码的输入、输出的测试结果如下:
由实测代码的输入、输出的测试结果可知:Scanner类的next()系列方法是可以脱离hasNext()系列方法而单独使用的。 也就是说hasNext()系列方法仅仅是为了:在next()系列方法执行前,检查输入项是否符合next()系列方法对输入项数据类型的要求。虽然next()系列方法是可以单独使用的,但在实际编码过程中我们最好还是在next()系列方法执行前通过hasNext()系列方法检查输入项的数据类型,以此减少程序报错。
3.2 Scanner类的next()系列方法实测代码2号
Scanner类的next()系列方法实测代码2号如下:
import java.util.Scanner;
public class ScannerTest {
public static void main(String[] args){
//System.in代表标准输入,即键盘输入到控制台终端
//该行代码表示创建一个通过键盘输入到控制台终端的Scanner对象
Scanner scanner=new Scanner(System.in);
System.out.println("请输入Scanner将读取的文本:");
System.out.println("刚输入的文本是:"+scanner.next());
System.out.println("刚输入的文本是:"+scanner.nextDouble());
System.out.println("刚输入的文本是:"+scanner.nextInt());
}
}
上述实测代码的输入、输出的测试结果如下:
由实测代码的输入、输出的测试结果可知:Scanner类的next()系列方法默认读取控制台终端及其缓存中第一个分隔符前面的输入项。同时因为被next()系列方法取出,该输入项将在缓存中消失。
3.3 Scanner类的nextLine()方法实测代码
Scanner类的nextLine()方法实测代码如下:
import java.util.Scanner;
public class ScannerTest {
public static void main(String[] args){
//System.in代表标准输入,即键盘输入到控制台终端
//该行代码表示创建一个通过键盘输入到控制台终端的Scanner对象
Scanner scanner=new Scanner(System.in);
System.out.println("请输入Scanner将读取的文本:");
System.out.println("刚输入的文本是:"+scanner.nextLine());
}
}
上述实测代码的输入、输出的测试结果如下:
由实测代码的输入、输出的测试结果可知:若有输入项,Scanner类的nextLine()方法则在不以空格符(Space)和制表符(Tab)为分隔符,而仅以回车(Enter)为分隔符
的条件下,以一行为单位返回String字符串类型的输入项。
4.Scanner类的useDelimiter()方法
Scanner类的useDelimiter()方法 | 方法详解 |
---|---|
public Scanner useDelimiter(String pattern) | 形式参数pattern是一个正则表达式,按照pattern来设定Scanner类的hasNext()和next()系列方法的分隔符。 |
我们只需记住下述实例代码scanner.useDelimiter("\n");
的用法及意义即可。
4.1 Scanner类的useDelimiter()方法实测代码
Scanner类的useDelimiter()方法实测代码如下:
import java.util.Scanner;
public class ScannerTest {
public static void main(String[] args){
//System.in代表标准输入,即键盘输入到控制台终端
//该行代码表示创建一个通过键盘输入到控制台终端的Scanner对象
Scanner scanner=new Scanner(System.in);
//将回车(Enter)作为Scanner类中的分隔符
scanner.useDelimiter("\n");
System.out.println("请输入Scanner将读取的文本:");
System.out.println("刚输入的文本是:"+scanner.next());
System.out.println("请输入Scanner将读取的double型数据:");
System.out.println("刚输入的文本是:"+scanner.nextDouble());
System.out.println("请输入Scanner将读取的double型数据:");
System.out.println("刚输入的文本是:"+scanner.nextDouble());
}
}
上述实测代码的输入、输出的测试结果如下:
由实测代码的输入、输出的测试结果可知:scanner.useDelimiter("\n");
将回车(Enter)作为Scanner类中的分隔符后,若碰到空格符和制表符,会把整行输入项当做String字符串类型存入控制台终端的缓存,从而导致控制台终端在打印3.4 5.6
时抛出异常InputMismatchException,这是因为3.4 5.6
中有空格符,所以3.4 5.6
是String字符串类型的,其不可作为double浮点型打印输出到控制台终端。
5.Scanner类的系列方法在实际中的用法
详情可见OJ中提交Java程序的一些套路。
参考文献:
[1]什么是阻塞和非阻塞,什么是同步和异步
[2]浅谈Java 线程阻塞及导致原理
[3]Java基础之线程阻塞、线程通信之消息队列
[4]什么是线程阻塞?为什么会出现线程阻塞?
[5]简书——Java Scanner类
[6] Java 流(Stream)、文件(File)和IO——Java Scanner 类
[7]CSDN——java中Scanner
[8]Java Scanner类的常用方法及用法(很详细)
[9]OJ平台(牛客等)中Java的输入方法
[10]Java在OJ平台提交的方式与基本套路
[11]OJ中提交Java程序的一些套路