【基础语法】Java Scanner hasNext() 和 hasNextLine() 的区别
OJ在线编程常见输入输出练习中默认模板
import java.util.Scanner;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
while (in.hasNextInt()) { // 注意 while 处理多个 case
int a = in.nextInt();
int b = in.nextInt();
System.out.println(a + b);
}
}
}
API源码解析
JDK 8
hasNext
功能:判断是否还有下一个值,通过检查缓冲区数据和标记(token)实现。
关键方法:
循环检查输入:使用一个
while
循环来不断检查输入源是否关闭(sourceClosed
变量)。在循环内部:
- 如果当前缓冲区中有可用的标记(通过
hasTokenInBuffer()
方法检查),则通过revertState(true)
恢复状态并返回true
。- 如果没有可用的标记,则调用
readInput()
方法从输入源中读取更多的输入到缓冲区中。处理循环结束后的状态:如果循环因为
sourceClosed
为true
而结束(即输入源已经关闭),则再次检查缓冲区中是否有可用的标记。
Note:有三种重载。
/**
* Returns true if this scanner has another token in its input.
* This method may block while waiting for input to scan.
* The scanner does not advance past any input.
*
* @return true if and only if this scanner has another token
* @throws IllegalStateException if this scanner is closed
* @see java.util.Iterator
*/
public boolean hasNext() {
ensureOpen();
saveState();
while (!sourceClosed) { //sourceClosed 跟踪输入源是否已经关闭
if (hasTokenInBuffer())
return revertState(true);
readInput();
}
boolean result = hasTokenInBuffer();
return revertState(result);
}
/**
* Returns true if the next complete token matches the specified pattern.
* A complete token is prefixed and postfixed by input that matches
* the delimiter pattern. This method may block while waiting for input.
* The scanner does not advance past any input.
*
* @param pattern the pattern to scan for
* @return true if and only if this scanner has another token matching
* the specified pattern
* @throws IllegalStateException if this scanner is closed
*/
public boolean hasNext(Pattern pattern) {
ensureOpen();
if (pattern == null)
throw new NullPointerException();
hasNextPattern = null;
saveState();
while (true) {
if (getCompleteTokenInBuffer(pattern) != null) {
matchValid = true;
cacheResult();
return revertState(true);
}
if (needInput)
readInput();
else
return revertState(false);
}
}
/**
* Returns true if the next token matches the pattern constructed from the
* specified string. The scanner does not advance past any input.
*
* <p> An invocation of this method of the form <tt>hasNext(pattern)</tt>
* behaves in exactly the same way as the invocation
* <tt>hasNext(Pattern.compile(pattern))</tt>.
*
* @param pattern a string specifying the pattern to scan
* @return true if and only if this scanner has another token matching
* the specified pattern
* @throws IllegalStateException if this scanner is closed
*/
public boolean hasNext(String pattern) {
return hasNext(patternCache.forName(pattern));
}
hasNextLine
功能:判断是否还有下一行。利用正则表达式匹配来查找行分隔符,并根据查找结果决定是否返回true。
方法实现:
保存当前状态:通过
saveState()
方法保存当前扫描器的状态,以便在方法结束时可以恢复到这个状态。这是为了确保方法调用前后扫描器的状态不变。查找下一行:使用
findWithinHorizon(linePattern(), 0)
方法尝试在输入中查找符合行模式的字符串。这里linePattern()
返回用于匹配行模式的正则表达式,0
表示没有超时限制。处理匹配结果:
- 如果找到了匹配项(即
result
不为null
),则进一步处理:
- 通过
this.match()
获取匹配结果(MatchResult
)。- 提取出匹配到的行分隔符(
lineSep
)。- 如果存在行分隔符,则从结果中去除行分隔符部分,并缓存处理后的结果。
- 如果不存在行分隔符,则直接缓存结果(此时可能是读取到了输入的末尾,但没有明确的行分隔符)。
- 如果没有找到匹配项,则不缓存任何结果。
恢复之前的状态:通过
revertState()
方法恢复扫描器到调用saveState()
时的状态。返回结果:如果找到了下一行的匹配项(即
result
不为null
),则返回true
;否则返回false
。
Note:只有一种类型的接口,无参。
/**
* Returns true if there is another line in the input of this scanner.
* This method may block while waiting for input. The scanner does not
* advance past any input.
*
* @return true if and only if this scanner has another line of input
* @throws IllegalStateException if this scanner is closed
*/
public boolean hasNextLine() {
saveState();
String result = findWithinHorizon(linePattern(), 0);
if (result != null) {
MatchResult mr = this.match();
String lineSep = mr.group(1);
if (lineSep != null) {
result = result.substring(0, result.length() -
lineSep.length());
cacheResult(result);
} else {
cacheResult();
}
}
revertState();
return (result != null);
}
结论用法
采用 hasXxxx 判断时,后面读取输入也要使用匹配的 nextXxxx 。
比如先用 hasNextLine 对控制台输入判断时,那么后续需要使用 nextLine 来处理输入。
- hasNext
- hasNextXxxx 判断下一个xxx类型
- hasNextLine 判断是否有下一行
关于 token 说明
上面有提到判断场景的token标记,那么这是什么概念和实际作用呢?
下一个标记可以先粗略理解为下一个数据(但数据类型未知)。
在Java的
Scanner
类中,术语“标记”(token)通常指的是输入流中可以被识别并作为一个单元处理的字符串或字符序列。Scanner
类使用分隔符(如空格、制表符、换行符等)来区分不同的标记。例如,当你使用Scanner
从控制台读取输入时,你可以通过空格、逗号或其他分隔符来分隔不同的标记。
Scanner 的使用案例
在Java输入控制台(即标准输入流,通常是键盘输入)中,Scanner
类通过以下方式实现标记的识别:
1、创建Scanner
对象:new并将其与标准输入流(System.in
)关联起来。
Scanner scanner = new Scanner(System.in);
2、设置分隔符(可选):默认情况下,Scanner
使用空白(空格、制表符、换行符等)作为分隔符。但是,你可以通过调用useDelimiter(String pattern)
方法来设置自定义的分隔符。
例如:逗号分隔标记
scanner.useDelimiter(",");
注意:设置分隔符会影响Scanner
识别标记的方式。
3、读取标记:一旦Scanner
对象被创建并配置了分隔符,你就可以使用next()
、nextInt()
、nextDouble()
等方法来读取下一个标记了。这些方法会返回输入流中下一个与指定类型匹配的标记。例如:
String token = scanner.next(); // 读取下一个标记作为字符串
int number = scanner.nextInt(); // 读取下一个标记并尝试将其解析为整数
4、检查是否有下一个标记:在读取标记之前,建议使用hasNext()
或hasNextLine()
方法先检查输入流中是否还有下一个标记或行。这些方法会返回一个布尔值,指示是否还有更多的输入可供读取。例如:
if (scanner.hasNext()) {
String token = scanner.next();
// 处理标记
}
5、关闭Scanner
对象:完成输入操作后,应该关闭Scanner
对象以释放资源。可以通过调用close()
方法来实现:(虽然大部分时间没关闭也不会像数据库那样报错)。
scanner.close();