Java Scanner 类精讲:控制台交互

📅 2026/7/9 1:04:42 👁️ 阅读次数 📝 编程学习
Java Scanner 类精讲:控制台交互

1.1 什么是 Scanner?

Scanner 是 Java 5.0 新增的基于字符流的解析工具类,位于java.util包下。它的核心设计目标是简化用户交互和数据读取场景的开发工作

  • 把底层的字节流(InputStream),自动转换为字符流,支持多种字符集编码;
  • 基于自定义分隔符(默认空格、换行符),将输入流切分成独立的令牌;
  • 将字符串类型的令牌,自动转换成开发者需要的基本数据类型(int、long、double、boolean 等)和字符串类型,内置正则校验逻辑。

1.2 Scanner 的核心应用场景

Scanner 不是用来处理高性能 IO 的工具类,它的核心适配场景,集中在人机交互和简单数据读取,这也是我们今天重点覆盖的范围:

场景分类具体用途工业级使用限制
控制台用户交互读取用户输入的的用户名、密码、年龄、订单号、确认指令(y/n)不推荐在高并发场景的后端服务中使用,仅用于控制台程序或内部工具的人机交互;
简单文件读取读取配置文件、小型日志文件、批量导入的文本数据(如 csv、txt)超过 100MB 的大文件,必须搭配缓冲区优化,或改用BufferedReader
网络请求流解析读取简单的 HTTP 响应、Socket 输入流,按分隔符拆分数据复杂网络协议或高并发场景,必须改用 Netty 等 NIO 框架;
字符串分词处理拆分复杂格式的字符串(如逗号分隔的 ID 列表、JSON 片段提取)复杂结构化数据,推荐使用 Jackson、Gson 等专业序列化工具;

1.3 Scanner vs BufferedReader:核心差异

很多开发者会混淆这两个读取工具类,二者在设计目标、底层逻辑、性能表现上完全不同,选型错误是线上故障的根源之一:

对比维度ScannerBufferedReader
核心设计目标简化数据解析:将字节流转换为字符流,自动拆分令牌、转换数据类型提升 IO 读取性能:为底层字符流提供 8KB 的缓冲能力,减少 IO 交互次数;
处理流类型字节流(InputStream)、字符流(Readable字符流(Reader),可通过InputStreamReader适配字节流;
数据读取方式基于自定义分隔符,将流拆分为令牌,再进行类型转换按字符数组、行、固定长度读取流,无内置令牌拆分逻辑;
数据类型转换内置支持基本数据类型、字符串转换,内置正则校验仅支持读取字符或字符串,需要手动进行类型转换;
读取效率慢:final 类,无锁设计,内置解析逻辑,单次读取涉及多次 IO 操作快:abstract 类,采用装饰器模式,单次读取涉及少量 IO 操作;
线程安全性非线程安全,多线程场景下必须做同步处理非线程安全,多线程场景下必须做同步处理;
工业级选型场景控制台交互、简单文件解析、低并发场景的数据流处理大文件读取、高并发场景的数据流处理、高性能 IO 场景;

【博主经验批注】:我在生产环境见过用 Scanner 读取 10GB 级别的日志文件,导致整个服务的 CPU 占用率飙升至 100%,最终触发 OOM。核心原因就是 Scanner 的设计目标是简化解析,而不是高性能读取。

2.1 基本使用步骤

Scanner 的工作逻辑非常固定,所有场景下的编码步骤都是标准的 4 步模板:

// 1. 引入Scanner类,必须在文件顶部导入 import java.util.Scanner; public class ConsoleInteractionDemo {   public static void main(String\[] args) {   // 2. 构造Scanner对象,传入底层字节流,这里用System.in读取控制台输入   Scanner scanner = new Scanner(System.in);       // 3. 调用nextXxx()方法,读取并解析用户输入,转换成目标类型   System.out.print("请输入您的用户名:");   String username = scanner.nextLine(); // 读取一行字符串,包含空格,直到遇到换行符       System.out.print("请输入您的年龄:");   int age = scanner.nextInt(); // 读取一个整数,自动跳过分隔符,做数字格式校验       System.out.print("请输入您的身高(单位:米):");   double height = scanner.nextDouble(); // 读取一个浮点数,自动做格式校验       System.out.print("请输入您的确认指令(y/n):");   String confirm = scanner.next(); // 读取一个令牌,默认以空格、换行符为分隔符       // 4. 关闭Scanner流,释放底层IO资源,避免资源泄漏   scanner.close();   } }

2.2 核心 API 用法差异解析

这是初学者最容易搞混的点,不同的读取方法,对分隔符的处理逻辑完全不同—— 理解这一点,是后续规避经典坑的前提。

方法名作用描述分隔符处理逻辑返回值适配场景
nextLine()读取一整行输入,包含空格,直到遇到换行符会读取当前位置到换行符之间的所有字符,会吃掉换行符本身,将光标移动到换行符之后的下一个位置字符串,不含末尾的换行符读取包含空格的用户名、地址、多行备注信息;
next()读取下一个有效令牌,默认以空格、换行符、制表符为分隔符会自动跳过开头的所有分隔符,然后读取直到下一个分隔符之前的所有字符,不会吃掉末尾的分隔符,光标停在分隔符之前字符串,不含首尾的分隔符读取不包含空格的指令、订单号、手机号、验证码;
nextInt()/nextLong()/nextDouble()读取并解析下一个令牌,转换成对应的基本数据类型内部调用next()读取字符串令牌,再尝试转换为目标类型,不会吃掉末尾的分隔符,光标停在分隔符之前对应的基本数据类型读取年龄、金额、数量、分页参数等数字类输入;
nextBoolean()读取并解析下一个令牌,转换为布尔类型内部调用next()读取字符串令牌,再校验是否为true/false(忽略大小写)布尔类型读取确认状态、开关标记、布尔类指令;
hasNext()/hasNextXxx()校验输入流中是否还有下一个令牌,是否匹配指定的正则表达式会阻塞等待用户输入,直到检测到分隔符或流结束标记,不会移动光标布尔类型,有下一个有效令牌则返回true循环读取输入、做输入合法性校验,避免异常;

2.3 正确关闭流资源

Scanner 实现了Closeable接口,必须调用close()方法关闭流,释放底层 IO 资源,否则会导致资源泄漏,长期运行会导致文件句柄或 socket 连接耗尽。

工业级场景下,必须使用try-with-resources语法,自动关闭流资源,保证异常发生时也能正常释放资源,不用手动调用close()方法。代码示例如下:

public class TryWithResourcesDemo {   public static void main(String\[] args) {   // try-with-resources语法:在try关键字后的括号中声明Scanner对象,try代码块执行完后会自动关闭   try (Scanner scanner = new Scanner(System.in)) {   System.out.print("请输入您的用户名:");   String username = scanner.nextLine();   System.out.println("您输入的用户名是:" + username);   } // 无论是否发生异常,这里都会自动调用scanner.close()方法,释放IO资源   } }