Java 标准输入详解:Scanner 与 BufferedReader 以及快速输入技巧

目次

1. 本文将学习的内容(快速结论)

在 Java 中处理 标准输入 有多种方式,但关键思想很简单:

根据你的目的选择输入方法。

你不需要从一开始就使用最快或最复杂的解决方案。本文将一步步解释 Java 标准输入,让你能够清晰地了解 何时以及为何 使用每种方法。

我们将从三个实用层面来讨论 Java 输入:

  • 针对初学者和小程序Scanner
  • 针对较大输入和稳定性能BufferedReader
  • 针对竞赛编程和超大输入FastScanner

1.1 应该使用哪一种?(Scanner vs BufferedReader vs 快速输入)

如果你时间紧迫,仅本节内容即可帮助你做决定。

1) 学习 Java 或处理小规模输入 → Scanner

  • 典型使用场景: wp:list /wp:list

    • Java 教程
    • 学校作业
    • 小型命令行工具
    • 优势: wp:list /wp:list

    • 编写非常简便

    • 易于阅读和理解
    • 缺点: wp:list /wp:list

    • 当输入规模变大时速度慢

何时使用 Scanner:

  • 输入规模小
  • 可读性比性能更重要

2) 大规模输入或需要稳定性能 → BufferedReader

  • 典型使用场景: wp:list /wp:list

    • 实际应用
    • 批处理
    • 包含大量输入行的编程练习
    • 优势: wp:list /wp:list

    • 比 Scanner 快得多

    • 行为可预测且稳定
    • 缺点: wp:list /wp:list

    • 必须手动解析数字和标记

何时选择 BufferedReader:

  • 输入规模大
  • 性能重要
  • 需要对输入处理拥有完整控制

3) 竞赛编程或海量输入 → FastScanner

  • 典型使用场景: wp:list /wp:list

    • 竞赛编程
    • 时间限制极其严格的问题
    • 优势: wp:list /wp:list

    • 极其快速

    • 缺点: wp:list /wp:list

    • 难以阅读

    • 调试困难
    • 不适合常规应用开发

何时适合使用 FastScanner:

  • 输入规模巨大
  • Scanner 或 BufferedReader 导致超时错误

1.2 本文适合的读者

本文面向各种层次的 Java 开发者。

初学者

  • 想了解 标准输入 的含义
  • 需要对 Scanner 的清晰解释
  • 常因输入相关的 bug 感到困惑

中级学习者

  • 已经了解 Scanner
  • 想弄清楚它为何变慢
  • 需要可靠的方式处理大规模输入

竞赛程序员

  • 需要快速的输入技术
  • 想要实用的竞赛模板

1.3 阅读完本文后你将能够

阅读完本文后,你将能够:

  • 理解 Java 标准输入(System.in)的真实含义
  • 为不同情境选择合适的输入方法
  • 正确使用 Scanner,避免常见陷阱
  • 使用 BufferedReader 高效读取大规模输入
  • 使用快速技术处理竞赛编程的输入
  • 自信地调试常见的输入相关问题

1.4 接下来会讲什么

在下一节中,我们将解释 Java 中“标准输入”到底是什么意思,从 System.in 开始。

理解这一基础后,ScannerBufferedReader 与快速输入方法之间的差异将更加清晰。

2. Java 中的“标准输入”是什么?

在学习如何使用 ScannerBufferedReader 之前,先了解 Java 中“标准输入”到底是什么意思 非常重要。许多与输入相关的困惑都源于跳过了这一基本概念。

2.1 标准输入 (System.in) 的作用

在 Java 中,标准输入 是程序启动时默认读取的数据来源。该来源由下面的对象表示:

System.in

Key points about System.in:

  • It represents an input stream provided by the operating system
    它代表由操作系统提供的输入流
  • Its type is InputStream
    它的类型是 InputStream
  • Java itself does not decide where the input comes from
    Java 本身并不决定输入来自何处

In other words, System.in is simply a data stream, not a “keyboard API”.
换句话说,System.in 只是一个数据流,而不是“键盘 API”。

2.2 Standard Input Is Not Always the Keyboard

2.2 标准输入并不总是键盘

A very common misconception is:

Standard input = keyboard input
标准输入 = 键盘输入

This is only partially true.
这仅部分正确。

When you run a program like this:

当你运行如下程序时:

java Main

the standard input is connected to the keyboard.
标准输入连接到键盘。

However, you can also run it like this:

但是,你也可以这样运行:

java Main < input.txt

In this case:

在这种情况下:

  • Input comes from a file
    输入来自文件
  • Not from the keyboard
    而不是键盘
  • But Java still reads it through System.in
    但 Java 仍然通过 System.in 读取

From the program’s perspective, there is no difference.
从程序的角度来看,没有区别

This is why standard input is often described as:

这就是为什么标准输入常被描述为:

“Whatever data is fed into the program at runtime”
“在运行时提供给程序的任何数据”

2.3 Why System.in Is Hard to Use Directly

2.3 为什么直接使用 System.in 很困难

Although System.in is powerful, it is not convenient to use directly.
虽然 System.in 功能强大,但直接使用并不方便

The reason is simple:

原因很简单:

  • System.in reads raw bytes
    System.in 读取原始字节
  • It does not understand: wp:list /wp:list

    • Lines
    • Numbers
    • 数字
    • Spaces
    • 空格
    • Text encoding
    • 文本编码

Example:

示例:

InputStream in = System.in;

At this level, you only deal with bytes, not meaningful values.
在这个层面上,你只处理字节,而不是有意义的值。

That is why Java provides wrapper classes that convert raw input into usable data.
这就是 Java 提供包装类将原始输入转换为可用数据的原因。

2.4 Input Handling Layers in Java

2.4 Java 中的输入处理层

Java input processing can be understood as a layered structure.
Java 的输入处理可以理解为分层结构。

[ Input source (keyboard, file, pipe) ]
                ↓
           System.in (InputStream)
                ↓
      Input helper classes
        ├ Scanner
        ├ BufferedReader
        └ Fast input implementations

Each layer has a clear responsibility:

每一层都有明确的职责:

  • System.in wp:list /wp:list

    • Low-level byte stream
    • 低级字节流
    • Scanner wp:list /wp:list

    • Easy token-based input (slow but simple)

    • 简单的基于标记的输入(慢但易用)
    • BufferedReader wp:list /wp:list

    • Fast line-based input

    • 快速的基于行的输入
    • FastScanner wp:list /wp:list

    • Performance-focused numeric input

    • 注重性能的数值输入

Understanding this structure explains why multiple input methods exist.
理解此结构可以解释为何存在多种输入方法。

2.5 Why Java Has Multiple Input Methods

2.5 为什么 Java 有多种输入方法

Java is used in many different contexts:

Java 在许多不同的场景中使用:

  • Education
    教育
  • Enterprise systems
    企业系统
  • Command-line tools
    命令行工具
  • Competitive programming
    竞技编程

Each context has different priorities:

每个场景都有不同的优先级:

  • Ease of use
    易用性
  • Readability
    可读性
  • Performance
    性能
  • Stability
    稳定性

Because of this, Java does not force a single “best” input method.
因此,Java 并不强制使用单一的“最佳”输入方法。

Instead, it offers multiple tools for different needs.
相反,它提供多种工具以满足不同需求

2.6 What Comes Next

2.6 接下来会讲什么

In the next section, we will start using Scanner, the most beginner-friendly way to read standard input.
在下一节中,我们将开始使用Scanner,这是最适合初学者读取标准输入的方式。

You will learn:

你将学习:

  • How to read strings
    如何读取字符串
  • How to read numbers
    如何读取数字
  • Common pitfalls that confuse beginners
    常见的让初学者困惑的陷阱

This will prepare you for understanding faster input methods later.
这将为你后续了解更快的输入方法做好准备。

3. Common Ways to Read Standard Input in Java (Overview)

3. Java 中读取标准输入的常见方式(概览)

Now that you understand what standard input is, let’s look at the three main ways to read it in Java.
现在你已经了解标准输入的概念,让我们看看在 Java 中读取它的三种主要方式

Before diving into code details, it is important to see the big picture.
在深入代码细节之前,先了解全局概念很重要。

Each method exists for a reason, and choosing the right one will save you time and frustration.
每种方法都有其存在的理由,选择合适的方式可以为你节省时间和避免挫败感。

3.1 Scanner: The Easiest and Most Beginner-Friendly Option

3.1 Scanner:最简单且最适合初学者的选项

Scanner is usually the first input class Java beginners learn.
Scanner 通常是 Java 初学者学习的第一个输入类。

Scanner sc = new Scanner(System.in);

With this single line, you can easily read:

只需这一行代码,你就可以轻松读取:

  • Strings
    字符串
  • Integers
    整数
  • Floating-point numbers
    浮点数

Key features of Scanner

Scanner 的关键特性

  • Pros wp:list /wp:list

    • Very easy to write and understand
    • 编写和理解都非常容易
    • Reads values directly as Java types
    • 直接将值读取为 Java 类型
    • Widely used in tutorials and textbooks
    • 在教程和教材中广泛使用
    • Cons wp:list /wp:list

    • Slow for large input

    • 对大规模输入较慢
    • Has some tricky behaviors beginners often encounter
    • 存在一些初学者常遇到的棘手行为

When Scanner is a good choice

何时适合使用 Scanner

  • Learning Java
    学习 Java
  • Small programs
    小型程序
  • Simple input requirements
    简单的输入需求

如果你的目标是了解 Java 语法和逻辑,Scanner 是一个很好的起点。

3.2 BufferedReader:快速且可靠的真实应用

BufferedReader 是在输入规模或性能变得重要时的标准选择。

BufferedReader br = new BufferedReader(
    new InputStreamReader(System.in)
);

与 Scanner 不同,BufferedReader 专注于高效读取整行

BufferedReader 的关键特性

  • 优点 wp:list /wp:list

    • 比 Scanner 快得多
    • 行为可预测且稳定
    • 适用于大规模输入
    • 缺点 wp:list /wp:list

    • 需要手动解析

    • 代码稍微复杂一些

何时选择 BufferedReader

  • 实际应用
  • 批处理
  • 输入行数众多的编程题

BufferedReader 通常被视为默认的专业选择

3.3 快速输入(FastScanner):用于竞赛编程

在竞赛编程中,即使是 BufferedReader 也可能太慢。

为了解决这个问题,许多开发者使用自定义快速输入类,通常称为 FastScanner

快速输入的特征

  • 使用缓冲区读取原始字节
  • 避免不必要的对象创建
  • 手动转换数字

优缺点

  • 优点 wp:list /wp:list

    • 极其快速
    • 适合海量输入
    • 缺点 wp:list /wp:list

    • 难以阅读

    • 难以调试
    • 不适用于常规应用

何时使用快速输入

  • 竞赛编程
  • 时间限制极其严格
  • 输入规模巨大

在普通软件开发中,快速输入很少有必要。

3.4 快速对比表

MethodEase of UseSpeedTypical Use
ScannerVery highLowLearning, small programs
BufferedReaderMediumHighReal applications
FastScannerLowVery highCompetitive programming

仅凭这张表就能帮助你决定使用哪种工具。

3.5 当你不确定时如何决定

如果你不确定该选哪种方法,请遵循以下规则:

  1. 这是用于学习还是小程序吗? wp:list /wp:list

    • 是 → Scanner
      2. 输入是否很大或性能重要? wp:list /wp:list

    • 是 → BufferedReader
      3. 这是竞赛编程题目吗? wp:list /wp:list

    • 是 → FastScanner

大多数真实场景都可以完美使用BufferedReader解决。

3.6 接下来会讲什么

在下一节中,我们将重点介绍如何正确使用 Scanner

你将学习:

  • 如何读取字符串
  • 如何读取数字
  • 常见的 Scanner 陷阱以及如何避免

这将帮助你从一开始就编写正确的输入处理代码。

4. 使用 Scanner 读取标准输入(从基础到实用技巧)

在本节中,我们将重点介绍Scanner,这是在 Java 中读取标准输入最适合初学者的方式。
我们不仅会展示如何使用它,还会解释为什么会出现某些问题,帮助你避免常见错误。

4.1 读取一行文本(nextLine

最简单的用例是读取整行文本。

Scanner sc = new Scanner(System.in);
String line = sc.nextLine();
System.out.println(line);
  • nextLine() 读取直到换行符之前的所有内容
  • 行内的空格会被保留

示例输入:

Hello Java World

结果:

Hello Java World

当你想读取句子或自由格式文本时,这种方法非常理想。

4.2 读取数字(nextIntnextLong 等)

Scanner 最大的优势之一是它可以直接读取数字

int n = sc.nextInt();
long x = sc.nextLong();
double d = sc.nextDouble();

如果输入是:

42

42 会直接作为 int 存储,无需手动转换。

为什么这很方便

  • 不需要 Integer.parseInt
  • 减少样板代码
  • 对初学者来说易于理解

4.3 著名的陷阱:nextInt() 后接 nextLine()

这是最常见的 Scanner 相关错误之一。

int n = sc.nextInt();
String s = sc.nextLine(); // often becomes empty

为什么会出现这种情况?

  • nextInt() 只读取数字本身
  • 换行符( \n )仍然留在输入缓冲区中
  • nextLine() 读取了剩余的换行符

结果是,s 成为一个空字符串。

4.4 如何解决 nextLine() 的问题

标准做法是 消费掉剩余的换行符

int n = sc.nextInt();
sc.nextLine(); // consume newline
String s = sc.nextLine();

这种模式非常常见,值得记住。

另外,你也可以完全避免混用 nextInt()nextLine(),而是把所有输入都读取为字符串。

4.5 读取空格分隔的值

Scanner 会自动把空格和换行视为分隔符。

输入:

10 20 30

代码:

int a = sc.nextInt();
int b = sc.nextInt();
int c = sc.nextInt();

这样就可以在不额外逻辑的情况下工作。

隐藏的代价

  • Scanner 在内部使用正则表达式
  • 这让它很灵活,但在处理大规模输入时会更慢

4.6 读取直到输入结束(EOF)

有时需要读取 直到没有更多数据 为止。

while (sc.hasNext()) {
    int value = sc.nextInt();
    System.out.println(value);
}

或者用于按行读取的情况:

while (sc.hasNextLine()) {
    String line = sc.nextLine();
    System.out.println(line);
}

这种模式在文件输入和在线评测中非常有用。

4.7 为什么 Scanner 在大输入时变慢

Scanner 之所以慢,并不是因为它“差”,而是因为它 为安全性和灵活性而设计

原因包括:

  • 正则表达式解析
  • 自动类型转换
  • 大量的输入校验

对于小规模输入,这些开销可以忽略不计。
但在大规模输入时,它会成为严重的性能瓶颈。

4.8 何时适合使用 Scanner

当以下情况成立时,Scanner 是一个不错的选择:

  • 你正在学习 Java
  • 输入规模较小
  • 代码可读性比运行速度更重要

如果性能成为关注点,就应该转向 BufferedReader

4.9 接下来会学什么

在接下来的章节中,我们将介绍 BufferedReader,这是快速且可靠的输入解决方案。

你将学习:

  • 按行读取输入
  • 手动数字解析
  • 高效处理大规模输入

5. 使用 BufferedReader 读取标准输入(快速且可靠)

本章节我们转向 BufferedReader,它在真实的 Java 项目和编程竞赛中被广泛使用。
相比 Scanner,代码稍多一些,但提供 更好的性能和更细的控制

5.1 最小示例:读取单行

BufferedReader 的基本使用方式如下:

BufferedReader br = new BufferedReader(
    new InputStreamReader(System.in)
);

String line = br.readLine();
System.out.println(line);
  • readLine() 读取输入
  • 当没有更多输入(EOF)时返回 null

这种明确的行为比 Scanner 更易于调试。

5.2 读取多行直到 EOF

当输入行数事先未知时,下面的模式是标准做法:

String line;
while ((line = br.readLine()) != null) {
    System.out.println(line);
}

为什么这种方式好

  • null 明确标识输入结束
  • 不需要额外的检查
  • 在文件输入和在线评测中非常常见

5.3 将字符串转换为数字

与 Scanner 不同,BufferedReader 总是读取 字符串,因此数值转换需要手动完成。

int n = Integer.parseInt(br.readLine());
long x = Long.parseLong(br.readLine());
double d = Double.parseDouble(br.readLine());

重要提示

  • 如果输入不是合法数字,会抛出 NumberFormatException
  • 这迫使你必须精确地处理输入格式

虽然起初可能觉得不方便,但它能让程序 更可预测

5.4 处理空格分隔的输入

空格分隔的数值非常常见。

输入:

10 20 30

方法一:使用 split(可读性优先)

String[] parts = br.readLine().split(" ");
int a = Integer.parseInt(parts[0]);
int b = Integer.parseInt(parts[1]);
int c = Integer.parseInt(parts[2]);
  • 易于理解
  • 适用于小到中等规模的输入
  • 由于正则表达式的使用,速度略慢

方法二:使用 StringTokenizer(性能导向)

StringTokenizer st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
int c = Integer.parseInt(st.nextToken());
  • split 更快
  • 在竞争编程中非常常见
  • 在现代 Java 中仍然完全有效

5.5 读取重复的空格分隔行

示例输入:

3
10 20
30 40
50 60

代码:

int n = Integer.parseInt(br.readLine());

for (int i = 0; i < n; i++) {
    StringTokenizer st = new StringTokenizer(br.readLine());
    int x = Integer.parseInt(st.nextToken());
    int y = Integer.parseInt(st.nextToken());
    System.out.println(x + y);
}

此模式经常出现在:

  • 编程测试
  • 算法题目
  • 数据处理工具

5.6 处理 IOException

BufferedReader 方法会抛出已检查异常,因此必须进行处理。

简单方法(学习/竞赛):

public static void main(String[] args) throws Exception {
    BufferedReader br = new BufferedReader(
        new InputStreamReader(System.in)
    );
}

生产环境方式:

try {
    BufferedReader br = new BufferedReader(
        new InputStreamReader(System.in)
    );
} catch (IOException e) {
    e.printStackTrace();
}

对于初学者来说,第一种方法通常已经足够。

5.7 为什么 BufferedReader 快

BufferedReader 快的原因在于:

  1. 它以大块方式读取数据(缓冲)
  2. 它避免了正则表达式的解析
  3. 它让你可以控制数据的解释方式

简而言之,它将以下两部分分离:

  • 读取
  • 解析

这种设计是它与 Scanner 的关键区别。

5.8 Scanner 与 BufferedReader 对比摘要

FeatureScannerBufferedReader
Ease of useVery highMedium
SpeedLowHigh
Line-based inputWeakExcellent
Numeric parsingAutomaticManual
Real-world usageLimitedVery common

5.9 接下来会讲什么

在下一节中,我们将探讨竞争编程中使用的 快速输入技术

你将学习到:

  • 即使是 BufferedReader 有时也会太慢的原因
  • 快速输入的概念性工作原理
  • 实用的 FastScanner 实现

6. 竞争编程的快速输入(FastScanner)

在竞争编程中,输入规模可能大到即使是 BufferedReader 也会成为瓶颈。
本节解释 为何需要快速输入、其工作原理以及何时应该(或不应该)使用它。

6.1 为什么 Scanner 会导致超时错误

竞争编程题目通常具有:

  • 数十万甚至上百万的数字
  • 严格的时间限制(1–2 秒)
  • 受限的执行环境

Scanner 在内部执行了许多检查:

  • 正则表达式解析
  • 自动类型转换
  • 广泛的校验

这些特性有助于安全,但会消耗性能。
因此,Scanner 常常在比赛中导致 TLE(超时)

6.2 当 BufferedReader 仍然不够时

BufferedReader 比 Scanner 快得多,但仍然:

  • 创建 String 对象
  • 将字符串拆分为标记
  • 从文本解析数字

当输入极其庞大时,这些步骤本身就可能太慢。

这就引出了另一种做法:

读取原始字节并手动转换为数字

6.3 快速输入的工作原理(概念层面)

快速输入技术通常遵循以下流程:

  1. 一次读取大块字节
  2. 将其存入字节缓冲区
  3. 跳过不必要的字符(空格、换行)
  4. 直接将数字字符转换为数值
  5. 避免创建不必要的对象

这样可以最小化:

  • 内存分配
  • 垃圾回收
  • CPU 开销

6.4 实用的 FastScanner 实现

下面是一个适用于竞赛的 简洁实用的 FastScanner

static class FastScanner {
    private final InputStream in = System.in;
    private final byte[] buffer = new byte[1 << 16];
    private int ptr = 0, len = 0;

    private int readByte() throws IOException {
        if (ptr >= len) {
            len = in.read(buffer);
            ptr = 0;
            if (len <= 0) return -1;
        }
        return buffer[ptr++];
    }

    int nextInt() throws IOException {
        int c;
        do {
            c = readByte();
        } while (c <= ' ');

        boolean negative = false;
        if (c == '-') {
            negative = true;
            c = readByte();
        }

        int value = 0;
        while (c > ' ') {
            value = value * 10 + (c - '0');
            c = readByte();
        }
        return negative ? -value : value;
    }
}

示例用法:

FastScanner fs = new FastScanner();
int n = fs.nextInt();

这种方法在处理大规模数值输入时极其快速。

6.5 FastScanner 的重要限制

快速输入 并非通用解决方案

缺点:

  • 难以阅读和维护
  • 调试困难
  • 不适合文本密集的输入
  • 对大多数应用来说是大材小用

仅在必要时使用 FastScanner,通常在竞赛中。

6.6 输入方法概述

  • Scanner → 学习、少量输入
  • BufferedReader → 实际应用、大量输入
  • FastScanner → 仅用于竞赛编程

选择满足性能需求的最简工具始终是最佳方案。

6.7 接下来内容

在下一节中,我们将使用简易决策指南总结 如何选择合适的输入方法

7. 如何选择合适的输入方法(快速决策指南)

到目前为止,你已经看到多种处理 Java 标准输入的方法。
本节帮助你 快速且自信地 决定在实际场景中使用哪种方法。

7.1 学习和小程序

推荐:Scanner

为什么 Scanner 在这里表现良好

  • 易于阅读和编写
  • 样板代码少
  • 符合大多数初学者教程

典型场景:

  • 学习 Java 基础
  • 小型命令行工具
  • 输入规模有限的练习
    Scanner sc = new Scanner(System.in);
    int n = sc.nextInt();
    

如果你的程序只读取少量输入,Scanner 完全足够。

7.2 大量输入和稳定性能

推荐:BufferedReader

为什么 BufferedReader 是默认的专业选择

  • 快速且可预测
  • 适用于大规模输入
  • 便于控制输入解析逻辑

典型场景:

  • 真实世界的应用
  • 批处理任务
  • 输入行数众多的编程题目
    BufferedReader br = new BufferedReader(
        new InputStreamReader(System.in)
    );
    

如果你不确定且性能重要,BufferedReader 是最安全的选择

7.3 竞赛编程和极大输入规模

推荐:FastScanner

FastScanner 存在的原因

  • 旨在避免超时 (TLE)
  • 高效处理海量数值输入

典型场景:

  • 竞赛编程比赛
  • 时间限制极其严格的问题
  • 极大数据集
    FastScanner fs = new FastScanner();
    int n = fs.nextInt();
    

在竞赛编程之外,这种做法通常没有必要。

7.4 简单决策流程

若有疑问,请遵循以下逻辑:

  1. 这是用于学习或小脚本吗? → 使用 Scanner
  2. 输入量大或性能重要吗? → 使用 BufferedReader
  3. 这是时间限制严格的竞赛题目吗? → 使用 FastScanner

实际上,大多数 Java 程序属于 第 2 步

7.5 常见误解

“最快的方法总是最好的”

这是错误的。

  • 更快的输入会降低可读性
  • 复杂的代码增加错误风险
  • 维护变得更困难

始终优先选择满足您需求的最简单方法

“Scanner 绝不应使用”

也是错误的。

Scanner 是一个优秀的学习工具,对于小任务完全有效。

7.6 接下来是什么

在下一节中,我们将探讨与 Java 标准输入相关的常见错误和故障排除提示

您将学习:

  • 输入为什么表现异常
  • 如何修复常见解析错误
  • 如何诊断性能问题

8. 常见错误和故障排除

即使您了解 Java 标准输入的基础知识,小错误也可能导致令人困惑的 bug 或性能问题。
本节收集了最常见的问题,解释了它们为什么发生,并展示了如何修复它们

8.1 输入似乎被跳过或缺失

症状

  • 字符串变量变为空
  • 输入被跳过而不等待用户输入

典型原因

这通常发生在将 nextInt()(或类似方法)与 Scanner 中的 nextLine() 混合使用时。

int n = sc.nextInt();
String s = sc.nextLine(); // becomes empty

解决方案

在调用 nextLine() 之前消耗剩余的换行符。

int n = sc.nextInt();
sc.nextLine(); // consume newline
String s = sc.nextLine();

或者,使用 BufferedReader 并手动处理解析。

8.2 程序永远等待输入

症状

  • 程序不终止
  • 在线评判提交从未完成

典型原因

  • 程序期望的输入多于提供的
  • EOF(输入结束)未正确处理

解决方案

使用 EOF 感知的输入循环。

String line;
while ((line = br.readLine()) != null) {
    // process line
}

始终仔细检查输入格式规范。

8.3 解析数字时出现 NumberFormatException

症状

  • 将字符串转换为数字时程序崩溃
    int n = Integer.parseInt(line);
    

典型原因

  • 前导或尾随空格
  • 空行
  • 输入中的意外字符

解决方案

在解析前清理输入。

line = line.trim();
int n = Integer.parseInt(line);

还要验证输入格式是否符合您的预期。

8.4 split() 产生意外结果

症状

  • 错误的令牌数量
  • 结果数组中的空字符串

典型原因

值之间的多个空格。

String[] parts = line.split(" ");

解决方案

使用处理多个空格的正则表达式。

String[] parts = line.trim().split("\\s+");

这适用于一个或多个空格、制表符或换行符。

8.5 程序太慢(时间限制超出)

症状

  • 程序在本地工作但在比赛中失败
  • 执行时间超过限制

典型原因

  • 对大输入使用 Scanner
  • 过度使用 split()
  • 频繁调用 System.out.println()

解决方案

  • 切换到 BufferedReader
  • 使用 StringTokenizer 代替 split
  • 使用 StringBuilderPrintWriter 批量输出

通常,输入速度才是真正的瓶颈,而不是算法。

8.6 对检查异常(IOException)的困惑

症状

  • IOException 相关的编译错误
  • 不确定在哪里添加 try-catch

简单解决方案(学习 / 比赛)

public static void main(String[] args) throws Exception {
    BufferedReader br = new BufferedReader(
        new InputStreamReader(System.in)
    );
}

生产解决方案

使用适当的 try-catch 块并优雅地处理错误。

8.7 字符编码问题

症状

  • 非英语文本显示损坏
  • 输入中的意外字符

原因

输入编码与 Java 默认编码不匹配。

解决方案

明确指定编码。

BufferedReader br = new BufferedReader(
    new InputStreamReader(System.in, "UTF-8")
);

这在读取文件或多语言输入时尤为重要。

8.8 快速故障排查清单

当输入表现异常时,请检查以下内容:

  1. 你的代码是否完全匹配输入格式?
  2. 是否正确处理了换行和空格?
  3. 是否安全地混用了 Scanner 方法?
  4. 输入规模是否对 Scanner 来说过大?
  5. 是否正确处理了 EOF?

系统地检查这些要点可以解决大多数问题。

8.9 接下来会讲什么

在下一节中,我们将回答关于 Java 标准输入常见问题 (FAQ)

这将帮助消除剩余疑惑,并强化最佳实践。

9. 常见问题解答 (FAQ)

本节回答与 Java 标准输入 相关的最常见问题,尤其是初学者和中级开发者经常提出的疑问。

9.1 哪个更好:Scanner 还是 BufferedReader?

这取决于你的目的。

  • 使用 Scanner 的情形: wp:list /wp:list

    • 你正在学习 Java
    • 输入规模较小
    • 可读性比性能更重要
  • 使用 BufferedReader 的情形: wp:list /wp:list

    • 输入规模较大
    • 性能至关重要
    • 你希望行为可预测

如果不确定,通常建议长期使用 BufferedReader

9.2 Scanner 真的是慢吗?

是的,对于大规模输入而言。

Scanner 设计的目标是:

  • 安全
  • 灵活
  • 易用

这些特性在处理大量数据时会导致速度变慢。
对于小规模输入,差异可以忽略不计。

9.3 为什么 nextLine() 会返回空字符串?

当在调用 nextInt()(或类似方法)之后调用 nextLine() 时会出现这种情况。

原因:

  • nextInt() 并不会消费换行符
  • nextLine() 读取了剩余的换行符

解决方案:

sc.nextLine(); // consume newline

或者避免混用基于标记的输入方法和基于行的输入方法。

9.4 应该使用 split() 还是 StringTokenizer

  • 使用 split() 的情形: wp:list /wp:list

    • 输入规模较小
    • 可读性更重要
  • 使用 StringTokenizer 的情形: wp:list /wp:list

    • 输入规模较大
    • 性能很重要

在竞技编程中,StringTokenizer 仍被广泛使用。

9.5 如何读取直到 EOF 的输入?

使用 BufferedReader

String line;
while ((line = br.readLine()) != null) {
    // process input
}

使用 Scanner

while (sc.hasNext()) {
    // process input
}

EOF 处理在文件输入和在线评测系统中很常见。

9.6 能在真实项目中使用 FastScanner 吗?

不推荐。

FastScanner 的缺点:

  • 难以阅读
  • 难以维护
  • 仅针对竞赛进行优化

对于真实项目,BufferedReader 在速度与可读性之间提供了最佳平衡

9.7 是否总是需要处理异常?

  • 对于学习和竞赛:public static void main(String[] args) throws Exception 是可以接受的。
  • 对于生产代码: wp:list /wp:list

    • 使用合适的 try-catch
    • 明确处理错误

9.8 输入很快,但输出很慢。该怎么办?

同样需要优化输出。

  • 避免频繁使用 System.out.println()
  • 使用 StringBuilder
  • 使用 PrintWriter 进行缓冲输出

输入和输出的性能应 一起 优化。

9.9 接下来会讲什么

在最后一节中,我们将 总结全部内容 并重申关键结论。

10. 最终总结

本文从入门概念到竞技编程中使用的高级技巧,系统地探讨了 Java 标准输入
让我们通过回顾最重要的要点来结束本篇文章。

10.1 三大关键要点

  • 小规模输入或学习目的Scanner
  • 大规模输入或真实项目BufferedReader
  • 竞技编程及极端输入规模FastScanner

在不同情境下选择合适的工具远比在所有情况下都使用最快的方法更重要。

10.2 如何避免对输入处理感到困惑

大多数关于 Java 输入的困惑来源于对以下内容的不了解:

  • 为什么会有多种输入方式
  • 每种方式各自的取舍是什么
  • 何时性能真的重要

一旦把输入处理视为设计选择,困惑就会消失。

10.3 初学者推荐的学习路径

如果你是 Java 新手,请按以下顺序学习:

  1. 使用 Scanner 学习输入
  2. 转向 BufferedReader 以获得更好的性能和控制
  3. 只有在进入竞技编程时才学习快速输入技巧

这条路径既能建立信心,又能养成正确的习惯。

10.4 实际使用技巧

  • 始终确认输入格式
  • 注意换行符和空格
  • 同时优化输入和输出
  • 当性能不佳时,先怀疑是输入速度问题

这些简单的习惯可以防止大多数与输入相关的 bug。

10.5 下一步该做什么

在掌握标准输入后,想要进一步提升 Java 技能,可以学习:

  • 标准输出优化(PrintWriterStringBuilder
  • 异常处理基础
  • 与输入配合使用的集合(ListMap
  • 用于算法的输入/输出设计

Java 标准输入看似简单,却是支撑每个 Java 程序的核心技能
精通它会让你的代码更快、更简洁、更可靠。