Java Scanner类的一些经典坑拆解

 更新时间:2026年07月18日 10:04:37   作者:落叶-IT  
Scanner自JDK 1.5起就被引入java.util包,但它常被误解为只能用来读控制台输入,下面这篇文章主要介绍了Java Scanner类一些经典坑拆解的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

坑 1:nextInt()/next()后紧跟nextLine(),输入被吞

这是 Scanner 最经典的坑,没有之一。

问题复现

编写如下代码,先读取年龄(整数),再读取用户名(字符串):

public class InputHungryDemo {
   public static void main(String\[] args) {
       try (Scanner scanner = new Scanner(System.in)) {
           System.out.print("请输入您的年龄:");
           int age = scanner.nextInt(); // 读取整数用户输入
           System.out.print("请输入您的用户名:");
          String username = scanner.nextLine(); // 读取字符串用户输入
           System.out.println("年龄:" + age + ",用户名:" + username);
      }
   }
}

运行结果:输入完年龄后,按下回车,程序直接跳过用户名输入步骤,输出了空的用户名,或者直接抛出异常。

底层原因分析

这是nextInt()nextLine()对分隔符的处理逻辑不一致导致的,根源在底层的缓冲区处理逻辑:

  • 当你输入18并按下回车时,控制台的输入缓冲区中,存储的实际字符是18\r\n(Windows 系统)或18\n(Linux/Mac 系统);
  • nextInt()方法的执行逻辑:从缓冲区中读取字符,直到遇到第一个非数字字符(这里是\r\n),停止读取,将读取到的字符串18转换为整数18,然后将光标停留在分隔符\r/\n的前面,不会吃掉这个分隔符;
  • 接下来执行nextLine()方法:它会从当前光标位置开始读取,直到遇到第一个换行符,而此时光标后面的第一个字符就是换行符,所以它直接读取到了一个空字符串,将光标移动到换行符之后,结束读取逻辑。

这就相当于,nextInt()把换行符留给了nextLine(),而nextLine()又恰恰以换行符作为结束标记,直接读取了空内容。

修复方案

有两种标准修复方案,本质都是统一分隔符处理逻辑,将残留的分隔符手动消费掉,或者用匹配的方法处理。

修复方案 1:在nextInt()后,额外调用一次nextLine(),吃掉残留的换行符

public class FixInputHungryDemo1 {
   public static void main(String\[] args) {
       try (Scanner scanner = new Scanner(System.in)) {
           System.out.print("请输入您的年龄:");
          int age = scanner.nextInt(); // 读取整数,光标停留在换行符之前
           // 【修复关键】额外调用一次nextLine(),吃掉残留的换行符,将光标移动到换行符之后
           scanner.nextLine();
           System.out.print("请输入您的用户名:");
           String username = scanner.nextLine(); // 正常读取用户名
           System.out.println("年龄:" + age + ",用户名:" + username);
       }
   }
}

修复方案 2:统一使用nextLine()读取所有输入,再手动转换数据类型

这种方案更工业级,因为它完全规避了不同方法对分隔符处理逻辑的差异:

public class FixInputHungryDemo2 {
   public static void main(String\[] args) {
      try (Scanner scanner = new Scanner(System.in)) {
           System.out.print("请输入您的年龄:");
           // 用nextLine()读取整行字符串,再手动转换为整数,搭配正则校验格式
           int age = Integer.parseInt(scanner.nextLine());
           System.out.print("请输入您的用户名:");
           String username = scanner.nextLine(); // 正常读取用户名
           System.out.println("年龄:" + age + ",用户名:" + username);
       }
   }
}

坑 2:nextLine()读取不到输入内容,读取了空字符串

这是坑 1 的衍生问题,根源是缓冲区中残留了未被消费的分隔符

问题复现

编写如下代码,先读取一个令牌,再读取一行字符串:

public class NextLineEmptyDemo {
   public static void main(String\[] args) {
       try (Scanner scanner = new Scanner(System.in)) {
           System.out.print("请输入一个指令:");
           String cmd = scanner.next(); // 读取一个令牌,比如输入:save,按下回车
           System.out.print("请输入备注信息:");
           String remark = scanner.nextLine(); // 直接读取到空字符串
           System.out.println("指令:" + cmd + ",备注:" + remark);
       }
   }
}

底层原因分析

next()方法读取到下一个分隔符(换行符)时,会停止读取,不会吃掉末尾的分隔符,光标停留在分隔符之前。接下来的nextLine()方法,直接读取到了这个残留的分隔符,返回空字符串,将光标移动到分隔符之后。

修复方案

和坑 1 的修复逻辑完全一致,在调用next()方法后,额外调用一次nextLine()方法,吃掉残留的分隔符,或者统一使用nextLine()方法读取所有输入。

坑 3:输入的格式不匹配,抛出InputMismatchException

这是校验逻辑不规范导致的常见异常。

问题复现

编写如下代码,读取用户输入的年龄:

public class InputMismatchDemo {
   public static void main(String\[] args) {
       try (Scanner scanner = new Scanner(System.in)) {
           System.out.print("请输入您的年龄:");
           int age = scanner.nextInt(); // 如果输入的不是整数,比如"abc",直接抛出异常
           System.out.println("年龄:" + age);
       }
   }
}

运行结果:如果用户输入的不是整数,比如abc18.5十八,程序会直接抛出InputMismatchException异常,没有任何友好提示,流程中断。

底层原因分析

nextInt()方法的底层逻辑,是调用next()读取字符串令牌,再尝试用Integer.parseInt()方法将字符串转换为整数。如果字符串的格式不匹配整数的规则,比如包含字母、特殊字符、小数点,或者超出int类型的取值范围,就会抛出InputMismatchException异常,这是一个非检查型异常,上层如果没有捕获,会直接中断线程的执行。

修复方案

工业级场景下,必须先调用hasNextXxx()方法,预校验输入格式是否匹配,再调用nextXxx()方法读取,搭配if-elsewhile循环,实现重试逻辑,给用户明确的错误提示。修复代码如下:

public class FixInputMismatchDemo {
   public static void main(String\[] args) {
       try (Scanner scanner = new Scanner(System.in)) {
           System.out.print("请输入您的年龄:");
           // 【修复关键】先校验下一个令牌是否为合法整数,循环重试,直到输入合法
           while (!scanner.hasNextInt()) {
               // 输入不匹配,读取并丢弃当前不合法的令牌,打印友好错误提示
               String invalidInput = scanner.next();
               System.out.printf("输入的「%s」不是合法整数,请重新输入:%n", invalidInput);
           }
           // 校验通过,再读取合法整数
           int age = scanner.nextInt();
           System.out.println("年龄:" + age);
       }
   }
}

坑 4:流被关闭后,继续读取 / 底层流被关闭,导致NoSuchElementException

这是资源释放顺序不当导致的异常。

问题复现

编写如下代码,先关闭 Scanner 流,再尝试读取输入:

public class NoSuchElementDemo {
  public static void main(String\[] args) {
       Scanner scanner = new Scanner(System.in);
       System.out.print("请输入用户名:");
       String username = scanner.nextLine();
       // 不当的资源关闭顺序:提前关闭了Scanner流
       scanner.close();
       System.out.print("请输入年龄:");
       int age = scanner.nextInt(); // 抛出异常,流已被关闭
   }
}

底层原因分析

调用scanner.close()方法时,会同时关闭 Scanner 底层包裹的InputStream(这里是System.in)。流被关闭后,无法再进行任何读取操作,后续调用任何nextXxx()方法,都会抛出NoSuchElementException异常。

修复方案

  • 必须保证所有读取操作完成后,再关闭流
  • 优先使用 try-with-resources 语法,由编译器自动管理流的关闭顺序,保证读取完成后再释放资源;
  • 多个 Scanner 包裹同一个底层流(如System.in)时,不能提前关闭,要在所有读取操作完成后,统一关闭一次。

坑 5:读取包含中文的文件 / 控制台输入,出现乱码问题

这是字符集编码不匹配导致的经典问题。

问题复现

编写如下代码,读取包含中文的 GBK 编码的文件:

public class CharsetMismatchDemo {
   public static void main(String\[] args) throws FileNotFoundException {
       // 读取GBK编码的文件,中文会乱码
       File file = new File("test\_gbk.txt");
       try (Scanner scanner = new Scanner(file)) {
           while (scanner.hasNextLine()) {
               String line = scanner.nextLine();
               System.out.println(line); // 中文乱码
           }
       }
   }
}

底层原因分析

Scanner默认使用当前操作系统的字符集编码读取底层流。比如,中文 Windows 系统的默认字符集是 GBK,而 Linux/Mac 系统的默认字符集是 UTF-8。如果文件的实际编码,和 Scanner 设置的编码不一致,就会出现字节转字符时的乱码问题。

修复方案

工业级场景下,必须手动指定与输入流匹配的字符集编码,不能依赖系统默认编码,保证跨平台、跨环境的一致性。

Java 10 及以上版本,提供了传入Charset的构造方法,直接指定编码格式;Java 8 及以下版本,需要用InputStreamReader适配流和编码格式。修复代码如下:

public class FixCharsetMismatchDemo {
   public static void main(String\[] args) throws FileNotFoundException {
       File file = new File("test\_gbk.txt");
       // 【修复关键1】手动指定与文件一致的GBK字符集编码
       try (Scanner scanner = new Scanner(file, "GBK")) {
           while (scanner.hasNextLine()) {
               String line = scanner.nextLine();
               System.out.println(line); // 中文正常显示
           }
       }
       // 【修复关键2】读取控制台输入时,指定全局标准编码为UTF-8
       try (Scanner scanner = new Scanner(System.in, StandardCharsets.UTF\_8.name())) {
           System.out.print("请输入包含中文的用户名:");
           String username = scanner.nextLine();
           System.out.println("用户名:" + username);
       }
   }
}

总结 

到此这篇关于Java Scanner类的一些经典坑拆解的文章就介绍到这了,更多相关Java Scanner类踩坑内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring 配置文件字段注入到List、Map

    Spring 配置文件字段注入到List、Map

    这篇文章主要介绍了Spring 配置文件字段注入到List、Map,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • Java使用BigDecimal精确运算浮点数

    Java使用BigDecimal精确运算浮点数

    这篇文章主要介绍了Java使用BigDecimal精确运算浮点数,帮助大家更好的处理浮点数数据,感兴趣的朋友可以了解下
    2020-10-10
  • windows 32位eclipse远程hadoop开发环境搭建

    windows 32位eclipse远程hadoop开发环境搭建

    这篇文章主要介绍了windows 32位eclipse远程hadoop开发环境搭建的相关资料,需要的朋友可以参考下
    2016-07-07
  • ZIP4j 压缩与解压的实例详解

    ZIP4j 压缩与解压的实例详解

    这篇文章主要介绍了ZIP4j 压缩与解压的实例详解的相关资料,希望通过本文能帮助到大家,需要的朋友可以参考下
    2017-10-10
  • springboot实现yml里的自定义配置方法

    springboot实现yml里的自定义配置方法

    这篇文章主要介绍了springboot实现yml里的自定义配置方法,主要介绍三种,字符串配置,数组配置和带默认值的配置,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • SpringBoot RestTemplate GET POST请求的实例讲解

    SpringBoot RestTemplate GET POST请求的实例讲解

    这篇文章主要介绍了SpringBoot RestTemplate GET POST请求的实例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Spring项目多实例部署ShedLock分布式定时任务锁框架介绍

    Spring项目多实例部署ShedLock分布式定时任务锁框架介绍

    spring shedlock用于在分布式服务的情况下执行定时任务,例如定时删除数据库中的一些数据,做数据迁移等等操作,这篇文章主要介绍了Spring项目多实例部署ShedLock分布式定时任务锁框架介绍的相关资料,需要的朋友可以参考下
    2026-07-07
  • Sentinel流控规则实现限流保护详解

    Sentinel流控规则实现限流保护详解

    这篇文章主要介绍了Sentinel流控规则实现限流保护,Sentinel是一个分布式系统的流量控制组件,它可以实现限流,流控,降级等功能,提高系统的稳定性和可靠性,感兴趣想要详细了解可以参考下文
    2023-05-05
  • 详解Java如何实现百万数据excel导出功能

    详解Java如何实现百万数据excel导出功能

    这篇文章主要为大家详细介绍了Java如何实现百万数据excel导出功能,文中的示例代码讲解详细,具有一定的借鉴价值,需要的可以参考一下
    2023-02-02
  • 一文详解Spring事务的实现与本质

    一文详解Spring事务的实现与本质

    这篇文章主要介绍了Spring中事务的两种实现方式:声明式事务、编程式事务以及他们的本质。文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-04-04

最新评论