该用 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() 才是轻量级的“是否有数据可读”判断。
立即学习“Java免费学习笔记(深入)”;
- 从
System.in读几个整数做计算,用new Scanner(System.in)一行搞定 - 需要按正则切分输入(如
useDelimiter("\\s+")),Scanner比手动split()更简洁 - 不关心编码细节、也不处理超大输入时,它降低了出错概率
BufferedReader 必须配合 InputStreamReader 吗
不一定,但几乎总是需要。因为 BufferedReader 构造函数只接受 Reader,而 System.in 是 InputStream。所以你得显式转码:
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 级日志——边界感比语法糖重要得多。










