S01-05 基础-Scanner
[TOC]
概述
Scanner:是 Java 1.5 引入的便捷输入处理工具类,位于java.util包下,核心作用是从控制台、文件、字符串、输入流等数据源中解析出不同类型的数据(基本类型、字符串等),底层封装了BufferedReader做缓冲处理,屏蔽了底层输入流的复杂操作,是Java入门和日常开发中最常用的输入方式,无需手动处理缓冲区、编码解析等底层细节。
核心特性
Scanner 核心特性:
数据源灵活:
支持System.in(控制台)、File(文件)、String(字符串)、InputStream(字节流)、Readable(字符流)等多种输入源。类型解析丰富:
内置方法直接解析int/long/float/double/boolean等所有基本类型,以及字符串,无需手动类型转换。分隔符可自定义:
默认以空白字符(空格、回车\n、制表符\t、换页符\f)为分隔符,也可通过正则表达式自定义分隔符(如逗号、分号)。输入验证机制:
提供hasNextXxx()系列方法,提前判断下一个输入是否为目标类型,避免输入不匹配导致的InputMismatchException异常。自动关闭支持:
实现AutoCloseable接口,可通过try-with-resources语法自动关闭资源,避免输入流泄漏。存在局限性:
线程不安全(@NotThreadSafe),大文件读取效率低于BufferedReader,不支持直接读取char类型(需间接处理)。
基本使用
基本使用步骤
使用Scanner的核心流程为导入包 → 创建对象 → 读取数据 → 关闭资源,四步缺一不可(导入包和关闭资源易被初学者忽略)。
完整基础示例(控制台输入):
// 1. 导入Scanner类,必须显式导入,否则编译报错
import java.util.Scanner;
public class ScannerBasic {
public static void main(String[] args) {
// 2. 创建Scanner对象,指定数据源为控制台标准输入System.in
Scanner scanner = new Scanner(System.in);
// 3. 读取数据:提示用户输入并解析
System.out.print("请输入一个整数:");
int num = scanner.nextInt(); // 读取整数
System.out.print("请输入一个字符串:");
String str = scanner.next(); // 读取无空格字符串
// 输出读取结果
System.out.println("你输入的整数:" + num);
System.out.println("你输入的字符串:" + str);
// 4. 关闭Scanner:释放底层输入流资源,避免内存泄漏
scanner.close();
}
}资源关闭的最佳实践
资源关闭的最佳实践:
手动调用close()可能因异常导致资源未关闭,Java 7+ 推荐使用try-with-resources语法,括号内创建的Scanner会在代码块执行完毕后自动调用close(),无需手动编写,且异常时也能保证关闭。
import java.util.Scanner;
public class ScannerAutoClose {
public static void main(String[] args) {
// try-with-resources:括号内实现AutoCloseable的对象自动关闭
try (Scanner scanner = new Scanner(System.in)) {
System.out.print("请输入你的姓名:");
String name = scanner.nextLine();
System.out.println("欢迎你:" + name);
} // 此处自动执行scanner.close(),无需手动写
}
}构造方法
Scanner 构造方法(指定数据源):
Scanner提供多个构造方法适配不同输入场景,核心常用构造方法如下(按使用频率排序),部分文件/流相关构造方法会抛出受检异常,需处理try-catch或throws。
| 构造方法 | 数据源说明 | 异常处理 | 适用场景 |
|---|---|---|---|
Scanner(InputStream source) | 字节输入流,最常用System.in | 无 | 控制台交互式输入 |
Scanner(String source) | 字符串数据源 | 无 | 测试输入、解析固定格式字符串 |
Scanner(File file) | 本地文本文件 | 抛出FileNotFoundException | 读取文本文件内容 |
Scanner(File file, String charset) | 本地文件+指定编码 | 抛出FileNotFoundException | 避免文件中文乱码 |
Scanner(Readable source) | 字符流(如BufferedReader) | 无 | 自定义字符输入流 |
构造方法使用示例:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ScannerSource {
public static void main(String[] args) throws FileNotFoundException {
// 1. 控制台输入(最常用)
Scanner sc1 = new Scanner(System.in);
// 2. 字符串数据源:按默认分隔符解析
Scanner sc2 = new Scanner("10 20.5 Java true");
int a = sc2.nextInt(); // 解析整数10
double b = sc2.nextDouble(); // 解析浮点数20.5
String s = sc2.next(); // 解析字符串Java
boolean flag = sc2.nextBoolean(); // 解析布尔值true
System.out.println(a + "," + b + "," + s + "," + flag); // 10,20.5,Java,true
// 3. 文件数据源:指定UTF-8编码避免中文乱码
File file = new File("test.txt"); // 项目根目录下的文本文件
Scanner sc3 = new Scanner(file, "UTF-8");
while (sc3.hasNextLine()) {
String line = sc3.nextLine(); // 逐行读取文件
System.out.println("文件内容:" + line);
}
// 关闭资源
sc1.close();
sc2.close();
sc3.close();
}
}核心方法
Scanner 核心方法(按功能分类):
Scanner的方法分为输入验证方法(hasNextXxx())和数据读取方法(nextXxx()),两者一一对应,推荐先验证再读取,避免输入类型不匹配的异常。所有方法都会自动跳过数据源中的空白字符(除非自定义分隔符)。
基本类型
基本类型:验证+读取方法:
适用于解析int/long/float等8种基本类型,布尔类型仅识别true/false(忽略大小写),其他类型需输入对应格式的数值(如浮点数需包含.或科学计数法)。
| 验证方法 | 读取方法 | 功能说明 | 输入示例 |
|---|---|---|---|
hasNextInt() | nextInt() | 验证/读取int类型 | 10、-5 |
hasNextLong() | nextLong() | 验证/读取long类型 | 100L、12345678901 |
hasNextFloat() | nextFloat() | 验证/读取float类型 | 3.14f、5.0 |
hasNextDouble() | nextDouble() | 验证/读取double类型 | 6.28、1.2e3 |
hasNextBoolean() | nextBoolean() | 验证/读取boolean类型 | true、FALSE |
hasNextByte() | nextByte() | 验证/读取byte类型(-128~127) | 100、-50 |
hasNextShort() | nextShort() | 验证/读取short类型(-32768~32767) | 2000、-1000 |
带验证的基本类型读取示例(避坑核心):
若直接调用nextInt()但用户输入非整数,会抛出InputMismatchException,必须用hasNextXxx()先判断,再读取,且无效输入需用next()丢弃,否则会陷入死循环。
import java.util.Scanner;
public class ScannerBasicType {
public static void main(String[] args) {
try (Scanner sc = new Scanner(System.in)) {
// 读取整数:循环验证,直到输入合法
System.out.print("请输入一个整数:");
while (!sc.hasNextInt()) {
System.out.print("输入错误!请重新输入整数:");
sc.next(); // 丢弃无效输入,关键步骤(否则死循环)
}
int num = sc.nextInt();
// 读取浮点数:同理验证
System.out.print("请输入一个浮点数:");
while (!sc.hasNextDouble()) {
System.out.print("输入错误!请重新输入浮点数:");
sc.next();
}
double d = sc.nextDouble();
System.out.println("合法整数:" + num + ",合法浮点数:" + d);
}
}
}字符串读取
字符串读取:next() vs nextLine()(重点区分,避坑核心):
Scanner提供两种字符串读取方法,差异极大,是初学者最易踩坑的点,核心区别在于是否识别空白字符和整行数据。
| 方法名 | 核心规则 | 终止符 | 是否包含空白字符 | 适用场景 |
|---|---|---|---|---|
next() | 1. 跳过开头所有空白字符;2. 读取到第一个空白字符时停止;3. 仅返回有效字符 | 空格/回车/制表符 | 否 | 读取单个单词、无空格字符串 |
nextLine() | 1. 从当前位置读取到换行符\n;2. 包含中间所有空白字符;3. 读取后丢弃换行符 | 换行符\n | 是 | 读取完整行、含空格的字符串 |
基础使用示例
基础使用示例:
import java.util.Scanner;
public class ScannerString {
public static void main(String[] args) {
try (Scanner sc = new Scanner(System.in)) {
System.out.print("请输入单个单词(无空格):");
String word = sc.next(); // 输入Hello World → 仅读取Hello
System.out.println("next()读取结果:" + word);
sc.nextLine(); // 丢弃上一步残留的换行符(后续讲解坑点)
System.out.print("请输入整行内容(可含空格):");
String line = sc.nextLine(); // 输入Hello World → 读取完整内容
System.out.println("nextLine()读取结果:" + line);
}
}
}最经典坑点
最经典坑点:nextXxx() 与 nextLine() 混用导致空行:
问题原因:nextInt()/nextDouble()/next()等方法读取完有效数据后,输入缓冲区中会残留一个换行符\n,后续调用nextLine()会直接读取这个换行符,返回空字符串。
import java.util.Scanner;
// 坑点演示:nextInt()后调用nextLine()返回空
public class ScannerPitfall {
public static void main(String[] args) {
try (Scanner sc = new Scanner(System.in)) {
System.out.print("请输入年龄:");
int age = sc.nextInt(); // 读取年龄后,缓冲区残留\n
System.out.print("请输入姓名:");
String name = sc.nextLine(); // 直接读取\n,返回空字符串
System.out.println("年龄:" + age + ",姓名:'" + name + "'"); // 姓名为空
}
}
}坑点解决方案(两种,推荐方案2):
方案1:读取完基本类型后,手动调用sc.nextLine()丢弃残留换行符
import java.util.Scanner;
public class ScannerFix1 {
public static void main(String[] args) {
try (Scanner sc = new Scanner(System.in)) {
System.out.print("请输入年龄:");
int age = sc.nextInt();
sc.nextLine(); // 关键:丢弃缓冲区的\n
System.out.print("请输入姓名:");
String name = sc.nextLine(); // 正常读取
System.out.println("年龄:" + age + ",姓名:" + name);
}
}
}方案2:统一使用nextLine()读取所有输入,再手动解析为目标类型(推荐)
彻底避免分隔符/换行符问题,所有输入先读为字符串,再通过Integer.parseInt()/Double.parseDouble()等方法转换为对应类型,是实际开发中最稳妥的方式。
import java.util.Scanner;
public class ScannerFix2 {
public static void main(String[] args) {
try (Scanner sc = new Scanner(System.in)) {
// 统一用nextLine()读取,再手动解析
System.out.print("请输入年龄:");
String ageStr = sc.nextLine();
int age = Integer.parseInt(ageStr); // 解析为int
System.out.print("请输入体重(kg):");
String weightStr = sc.nextLine();
double weight = Double.parseDouble(weightStr); // 解析为double
System.out.print("请输入家庭地址(可含空格):");
String address = sc.nextLine(); // 直接读取字符串
System.out.printf("年龄:%d,体重:%.1f,地址:%s\n", age, weight, address);
} catch (NumberFormatException e) {
System.err.println("输入类型错误,请输入合法的数值!");
}
}
}读取字符
读取字符(char):无直接方法,间接实现:
Scanner没有提供nextChar()方法,若需读取单个字符,需通过next()或nextLine()读取字符串后,调用charAt(0)获取字符串的第一个字符。
import java.util.Scanner;
public class ScannerChar {
public static void main(String[] args) {
try (Scanner sc = new Scanner(System.in)) {
System.out.print("请输入一个单个字符:");
char ch = sc.next().charAt(0); // 读取字符串→取索引0的字符
System.out.println("你输入的字符:" + ch);
System.out.println("该字符的ASCII码:" + (int) ch);
}
}
}逐行读取
逐行读取:hasNextLine() + nextLine():
适用于读取文件、控制台多行输入场景,hasNextLine()判断是否还有下一行数据,nextLine()读取整行,是处理文本的常用组合。
import java.util.Scanner;
// 控制台多行输入,输入exit退出
public class ScannerReadLine {
public static void main(String[] args) {
try (Scanner sc = new Scanner(System.in)) {
System.out.println("请输入内容(输入exit退出):");
while (sc.hasNextLine()) { // 判断是否有下一行
String line = sc.nextLine(); // 读取整行
if ("exit".equals(line)) {
System.out.println("程序退出!");
break;
}
System.out.println("你输入的内容:" + line);
}
}
}
}通用判断
通用判断:hasNext() + next():
hasNext():判断数据源中是否还有下一个有效标记(按分隔符分割的内容),返回布尔值;next():读取下一个有效标记,返回字符串; 两者是Scanner的基础方法,所有hasNextXxx()/nextXxx()都是基于此扩展。
import java.util.Scanner;
public class ScannerHasNext {
public static void main(String[] args) {
try (Scanner sc = new Scanner("Java Python C++ Go")) {
while (sc.hasNext()) { // 判断是否有下一个标记
String lang = sc.next(); // 读取下一个标记
System.out.println("编程语言:" + lang);
}
}
}
}进阶:自定义分隔符
Scanner 进阶用法:自定义分隔符:
Scanner默认以空白字符为分隔符,可通过useDelimiter(String regex)方法自定义分隔符(支持正则表达式),适用于解析固定格式的数据源(如CSV文件、逗号/分号分隔的日志、自定义协议数据)。
单个分隔符
基本自定义:单个分隔符:
如以逗号、分号、**竖线|**为分隔符,直接传入字符即可。
import java.util.Scanner;
public class ScannerDelimiter1 {
public static void main(String[] args) {
// 数据源:逗号分隔的字符串
Scanner sc = new Scanner("10,20.5,张三,true");
sc.useDelimiter(","); // 自定义分隔符为逗号
// 按逗号解析不同类型数据
int num = sc.nextInt();
double d = sc.nextDouble();
String name = sc.next();
boolean flag = sc.nextBoolean();
System.out.printf("num:%d, d:%.1f, name:%s, flag:%b\n", num, d, name, flag);
// 输出:num:10, d:20.5, name:张三, flag:true
sc.close();
}
}多个分隔符
高级自定义:多个分隔符(正则表达式):
若数据源中有多种分隔符(如同时有逗号和分号),可传入正则表达式[分隔符1|分隔符2],匹配任意一个分隔符。
import java.util.Scanner;
public class ScannerDelimiter2 {
public static void main(String[] args) {
// 数据源:逗号+分号混合分隔
Scanner sc = new Scanner("30;40.6,李四,false|50");
sc.useDelimiter("[,;|]"); // 正则:匹配逗号、分号、竖线中的任意一个
while (sc.hasNext()) {
System.out.println(sc.next());
}
/* 输出: 30 40.6 李四 false 50 */
sc.close();
}
}还原默认分隔符
还原默认分隔符:
若自定义分隔符后需恢复默认的空白字符分隔,调用useDelimiter("\\s+")即可(\\s+是正则,匹配一个或多个空白字符)。
sc.useDelimiter("\\s+"); // 还原默认空白字符分隔符操作文件
Scanner 操作文件(完整示例):
Scanner可轻松读取文本文件内容,核心是指定文件路径+编码,避免中文乱码,结合hasNextLine()+nextLine()逐行读取,是小文件读取的便捷方式(大文件推荐BufferedReader)。
完整文件读取示例(处理异常+指定UTF-8):
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ScannerReadFile {
public static void main(String[] args) {
// 文件路径:相对路径(项目根目录)或绝对路径(如C:/test.txt)
File file = new File("test.txt");
// try-with-resources 自动关闭Scanner
try (Scanner sc = new Scanner(file, "UTF-8")) {
int lineNum = 1;
System.out.println("文件内容(行号+内容):");
while (sc.hasNextLine()) {
String line = sc.nextLine();
System.out.println(lineNum + ":" + line);
lineNum++;
}
} catch (FileNotFoundException e) {
System.err.println("文件读取失败:" + e.getMessage());
System.err.println("请检查文件路径是否正确,或文件是否存在!");
}
}
}常见问题与避坑指南
Scanner 常见问题与避坑指南:
输入类型不匹配:InputMismatchException
- 原因:直接调用
nextInt()/nextDouble()等方法,用户输入非对应类型数据(如输入"abc"却读int)。 - 解决方案:先调用
hasNextXxx()验证,再读取,无效输入用next()丢弃。
- 原因:直接调用
nextLine() 读取空字符串(混用坑点)
- 原因:
nextXxx()读取后缓冲区残留换行符\n。 - 解决方案:手动调用
sc.nextLine()丢弃换行符,或统一用nextLine()读取后手动解析(推荐)。
- 原因:
中文乱码(控制台/文件)
- 控制台乱码:配置IDE控制台编码为UTF-8(IDEA:Run/Debug Configurations → VM options添加
-Dfile.encoding=UTF-8)。 - 文件乱码:创建Scanner时显式指定编码为
UTF-8,如new Scanner(file, "UTF-8")。
- 控制台乱码:配置IDE控制台编码为UTF-8(IDEA:Run/Debug Configurations → VM options添加
读取大文件效率低
- 原因:Scanner底层虽有缓冲,但包含大量类型解析逻辑,比纯缓冲的
BufferedReader耗时。 - 解决方案:读取GB级大文件时,改用
BufferedReader或Java 8+的Files.lines()。
- 原因:Scanner底层虽有缓冲,但包含大量类型解析逻辑,比纯缓冲的
线程不安全
- 原因:Scanner的方法未做同步处理,多线程共享一个Scanner对象会导致数据读取异常。
- 解决方案:多线程环境下,每个线程创建独立的Scanner对象,或使用
ThreadLocal<Scanner>隔离。
关闭Scanner后,System.in被关闭
- 原因:Scanner关联
System.in时,调用sc.close()会同时关闭底层的System.in输入流,后续无法再读取控制台输入。 - 解决方案:若程序中多次使用控制台输入,仅在程序最后关闭Scanner,或复用同一个Scanner对象。
- 原因:Scanner关联
无限循环(未丢弃无效输入)
- 原因:
hasNextXxx()判断为false时,未调用sc.next()丢弃无效输入,导致hasNextXxx()一直返回false,循环无法退出。 - 解决方案:在
while (!sc.hasNextXxx())中,调用sc.next()丢弃无效输入。
- 原因:
对比其他输入方式
Scanner 与其他输入方式的对比:
Java中常用的控制台/文件输入方式有多种,Scanner与其他方式的核心对比如下,可根据场景选择:
| 输入方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Scanner | API简洁、支持多类型解析、易用性强 | 大文件效率低、线程不安全 | 入门学习、日常开发、小规模输入/小文件读取 |
| System.in.read() | 底层、无依赖、无需导入包 | 仅读单个字节、需处理编码、中文乱码 | 底层字节输入、简单字符读取 |
| BufferedReader | 效率高、缓冲读取、支持逐行 | 仅读字符串、需手动解析类型 | 大文件读取、高性能场景 |
| Console 类 | 支持密码隐藏(readPassword()) | 仅控制台环境、IDE中可能返回null | 控制台敏感信息输入(密码/验证码) |
核心使用总结
Scanner 核心使用总结:
- 核心流程:导入
java.util.Scanner→ 创建对象指定数据源 → 先hasNextXxx()验证再nextXxx()读取 → 关闭资源(优先try-with-resources)。 - 字符串读取:优先区分
next()(无空格)和nextLine()(含空格),避免与基本类型方法混用,推荐统一用nextLine()后手动解析。 - 自定义分隔符:解析固定格式数据时,用
useDelimiter()指定分隔符,支持正则表达式匹配多分隔符。 - 文件读取:小文件用Scanner+
UTF-8编码,大文件改用BufferedReader。 - 异常处理:必须处理
InputMismatchException(类型不匹配)、FileNotFoundException(文件不存在)、NumberFormatException(手动解析失败)。 - 避坑核心:验证后读取、丢弃无效输入、处理换行符、指定UTF-8编码、避免多线程共享。
掌握Scanner的以上用法,能轻松解决Java入门和日常开发中90%以上的输入处理需求,是Java基础中必备的工具类技能。