在Java里Scanner和BufferedReader如何选择_Java输入方式对比说明

该用 BufferedReader 而不是 Scanner 的场景是:读取大量文本、性能敏感或需精确控制换行符时;因其无状态陷阱、行为可预测且速度快 2–5 倍,而 Scanner 适合算法题、简单解析等省事场景。

什么时候该用 BufferedReader 而不是 Scanner

当你要读取大量文本(比如几百MB日志、大文件逐行处理)、对性能敏感,或者需要精确控制换行符行为时,BufferedReader 是更稳妥的选择。Scanner 内部其实也用了 BufferedReader,但额外封装了词法分析逻辑,带来开销。

常见错误现象:Scanner.nextLine()nextInt() 后突然“跳过”输入——这是因为 nextInt() 不消费换行符,而 nextLine() 立刻读到那个残留的 \n。这种问题在交互式输入中高频出现,本质是状态不一致,而非 bug。

  • BufferedReader 没有这类“混合读取”的状态陷阱,它只提供 readLine() 和底层 read(),行为可预测
  • 读取整行内容(如用户输入命令、CSV 行、JSON 行)优先选 BufferedReader.readLine()
  • 需要按字符/字节数组读取、或配合 InputStreamReader 指定编码时,BufferedReader 更直接

什么场

景下 Scanner 更省事

快速写算法题、教学示例、或只需解析简单空格分隔的数值/字符串时,Scanner 的语法糖确实减少样板代码。它的 hasNextInt()nextDouble() 等方法自带类型校验和跳过空白逻辑。

但要注意:这些便利是有代价的。例如 Scanner.hasNextLine() 实际会触发一次预读(可能阻塞),而 BufferedReader.ready() 才是轻量级的“是否有数据可读”判断。

  • System.in 读几个整数做计算,用 new Scanner(System.in) 一行搞定
  • 需要按正则切分输入(如 useDelimiter("\\s+")),Scanner 比手动 split() 更简洁
  • 不关心编码细节、也不处理超大输入时,它降低了出错概率

BufferedReader 必须配合 InputStreamReader

不一定,但几乎总是需要。因为 BufferedReader 构造函数只接受 Reader,而 System.inInputStream。所以你得显式转码:

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));

漏掉 InputStreamReader 会导致编译失败;漏掉 StandardCharsets.UTF_8 则可能在中文 Windows 或非 UTF-8 终端里读出乱码(默认用平台编码,不可靠)。

  • 文件读取可用 Files.newBufferedReader(path, UTF_8),更安全
  • 若确定环境全是 ASCII,用默认构造也可,但不推荐
  • 不要用 new BufferedReader(new FileReader(...)) —— 它不支持指定编码,已过时

性能差异到底有多大

在纯读行场景下,BufferedReader.readLine() 通常比 Scanner.nextLine() 快 2–5 倍(实测百万行文本)。差距主要来自 Scanner 每次调用都要检查分隔符、更新内部状态、处理分组缓存。

但真实瓶颈往往不在这里。如果你发现输入慢,先确认是不是磁盘 I/O、终端回显延迟、或 System.in 被重定向到了低速流——而不是急着换类。

  • 微基准测试容易误导:用 System.nanoTime() 测单次调用意义不大,要测批量吞吐
  • Scanner 的缓冲区大小默认只有 1024 字节,可通过反射修改,但没必要
  • 真正影响响应速度的,常是 System.in 的行缓冲模式(比如未敲回车就不触发读)
实际项目里,BufferedReader 是 IO 层的事实标准,Scanner 更像一个教学/脚本工具。别在服务端代码里用 Scanner 解析请求体,也别在批处理里用它读 GB 级日志——边界感比语法糖重要得多。