一、問題背景
博主最近在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程序的一些套路