Scanner 类的使用
定义
public final class Scanner implements Iterator<String>, Closeable{}
构造方法
Scanner 只是"中间层",构造时把数据源交给它:
| 构造方法 | 数据源 | 典型场景 |
|---|---|---|
new Scanner(System.in) |
键盘 | 控制台交互(最常见) |
new Scanner("1,2,3") |
字符串 | 拆分/解析一段文本 |
new Scanner(new File("a.txt")) |
文件 | 简易读文件(抛 FileNotFoundException) |
new Scanner(Path, Charset) |
文件 | JDK 10+,可指定编码 |
两种读取模式
Token 模式(next() / nextInt() / nextDouble() …):
- 统一规则:跳过前导空白 → 取一段连续非空白 → 停
- 空格、制表符、换行都视为前导空白,因此可以跨行读(例:上一行末尾的
25后面直接跨行读下一行的数字) - 末尾换行符不消耗——这是设计出来的:分隔符留给下一次调用处理
行模式(nextLine()):
- 规则:从当前位置读到第一个换行(含空格),连换行一起消耗,返回不含换行的整行
方法
hasNextXxx = 一一对应的"先探测"保险栓,不消耗输入:
| 读取 | 先探测 | 说明 |
|---|---|---|
next() |
hasNext() |
读一个词,遇空白停 |
nextInt() |
hasNextInt() |
⭐ 使用频率最高 |
nextLong() |
hasNextLong() |
大整数(超 int 范围) |
nextDouble() |
hasNextDouble() |
小数(受 Locale 影响,见 §八) |
nextBoolean() |
hasNextBoolean() |
只认 "true" / "false" |
nextBigInteger() |
hasNextBigInteger() |
任意大整数 |
nextLine() |
hasNextLine() |
读整行,返回 String |
nextFloat() nextShort() nextByte() |
同理 hasNextXxx() |
用得少,知道即可 |
细节分析:nextInt() + nextLine()
缓冲区三步推演
键盘输入两行 25 和 Tom,缓冲区里的字符(\n = 看不见的换行符):
[2][5][\n][T][o][m][\n]
① nextInt() 执行完
[2][5][\n][T][o][m][\n]
^^^^^^
^ 读位置:换行只是分隔符,没被消耗,留在缓冲区
→ 返回 25
② 紧接着第 1 次 nextLine()
[2][5][\n][T][o][m][\n]
^^^^^^^^^^
^ 读位置:当前位置到第一个换行 = 空!
→ 返回 ""(空串,只吞掉了那个换行)
③ 再来第 2 次 nextLine()
[2][5][\n][T][o][m][\n]
^^^^^^^^^^^^^^^^^^^^^^
^ 读位置:全部消耗完
→ 返回 "Tom"(连同行尾换行一起消耗)
根因一句话:token 方法"拿数不拿行尾",行方法"见换行就收"——两边规则各自都对,混用时对不上才出坑。
修复(二选一)
// 方式一:补一句 nextLine() 吞掉残留换行(最常用)
int age = sc.nextInt();
sc.nextLine(); // 吞换行
String name = sc.nextLine(); // 正常读到 "Tom Smith"
// 方式二:数字也用 nextLine() 读,自己转型
int age = Integer.parseInt(sc.nextLine().trim());
String name = sc.nextLine();
为什么 Java 要这样设计?
token 方法"只拿属于自己的,分隔符留给下一家"——这样 "1 2 3" 能一个一个读、"12\n34" 跨行也能连续读,不丢数据。
C 的 scanf("%d") 也是同样的规则,所以 scanf 后接 fgets() 读到空行是 C 里的同款经典坑,根因一模一样,可以直接用 C 的心智模型。
close() 为什么要放到最后
使用完Scanner后,我们一定要记得将它关闭!
因为使用Scanner本质上是打开了一个IO流,如果不关闭的话,它将会一直占用系统资源。
但是注意一旦你关闭后,就算在sc.close()这行代码后你再重新new Scanner(System.in),那也不能重新再打开一个扫描器了
所以大家一定要在用不到扫描器之后再关闭,即把sc.close()代码放到最后。
next() 会自动调用 hasNext() 吗?
结论:不会。 只时共用底层 readInput() 作为读缓冲。
源码证据(JDK 24,java.util.Scanner 真实源码)
// next(): 取走一个 token;没数据 → 抛异常,绝不返回 null
public String next() {
ensureOpen();
clearCaches(); // 一上来就清掉 hasNext 的缓存
modCount++;
while (true) {
String token = getCompleteTokenInBuffer(null); // 注意:不是 hasNext()
if (token != null) {
matchValid = true;
skipped = false;
return token;
}
if (needInput)
readInput(); // 也会阻塞等键盘
else
throwFor(); // → NoSuchElementException
}
}
// hasNext(): 偷看一眼再恢复现场,不消耗
public boolean hasNext() {
ensureOpen();
saveState(); // 存现场
modCount++;
while (!sourceClosed) {
if (hasTokenInBuffer()) {
return revertState(true); // 恢复现场 → 不消耗
}
readInput();
}
boolean result = hasTokenInBuffer();
return revertState(result);
}
关键差异就是 saveState() / revertState():hasNext 是"看一眼再还原",next 是"取走并把 position 往前推"。
性能实测:Scanner 到底多慢
ScannerBench.java,同一份内存数据(100 万个整数,排除 I/O 噪音,第 2 轮成绩):
Scanner : 238 ms
BufferedReader: 48 ms → 约慢 4~5 倍
原因:Scanner 每读一个 token 都要走一遍正则匹配 + 类型判断,开销是"每个数字"级别的;BufferedReader 只负责把字符搬进缓冲区,解析交给 parseInt 一步到位。
结论:
- 几千几万个输入随便用 Scanner,代码干净值这个价
- OJ 上数据量到 10⁵ 以上(一次读几十万个数)再换 BufferedReader,现在不用急
- LeetCode 核心代码模式不写 main,用不到 Scanner;只有 ACM 模式(如牛客部分题)和自己写本地测试才需要

浙公网安备 33010602011771号