在编程竞赛中,输入输出效率至关重要。Java 的 `Scanner` 和 `System.out.println` 虽然简单,但在处理大规模数据时会严重拖慢速度。以下是 **竞赛专用输入输出模板** 及其原理详解,助你轻松应对高频 I/O 场景。
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
// 初始化输入输出
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st;
// 读取第一行数据(示例:N 和 Q)
st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken());
int Q = Integer.parseInt(st.nextToken());
// 读取第二行数据(示例:N个整数)
int[] arr = new int[N];
st = new StringTokenizer(br.readLine());
for (int i = 0; i < N; i++) {
arr[i] = Integer.parseInt(st.nextToken());
}
// 处理 Q 次操作
while (Q-- > 0) {
st = new StringTokenizer(br.readLine());
String op = st.nextToken();
if (op.equals("1")) {
int x = Integer.parseInt(st.nextToken());
int y = Integer.parseInt(st.nextToken());
// 处理操作1...
} else {
int x = Integer.parseInt(st.nextToken());
// 处理操作2...
bw.write(result + "\n"); // 使用 BufferedWriter 输出
}
}
// 最终刷新输出
bw.flush();
}
}
```
---
1. **BufferedReader vs Scanner**:
- `Scanner` 会进行复杂的正则解析和类型转换,效率极低。
- `BufferedReader` 直接读取原始字节流,通过 `readLine()` 整行读取,减少 I/O 次数。
2. **StringTokenizer 分割字符串**:
- 使用 `StringTokenizer` 分割字符串比 `split()` 更快,直接操作字符数组。
- 通过 `nextToken()` 逐个获取分割后的字段,内存占用低。
3. **BufferedWriter 批量输出**:
- `System.out.println` 每次调用都会触发 I/O,高频使用极慢。
- `BufferedWriter` 会缓存输出内容,调用 `flush()` 时一次性写入,减少系统调用。
---
#### 1. 异常处理简化
- 直接 `throws IOException` 避免 `try-catch`,竞赛代码允许这种写法。
- 生产代码不推荐,但竞赛中可极大简化代码。
#### 2. 高效读取不同类型数据
- **整数读取**:
```java
int x = Integer.parseInt(st.nextToken());
```
- **长整型读取**:
```java
long x = Long.parseLong(st.nextToken());
```
- **浮点数读取**:
```java
double x = Double.parseDouble(st.nextToken());
```
#### 3. 处理多行输入
```java
// 读取 N 行数据
for (int i = 0; i < N; i++) {
st = new StringTokenizer(br.readLine());
// 处理每行数据...
}
```
#### 4. 输出优化
- 使用 `bw.write()` 代替 `System.out.println`。
- 注意手动添加换行符 `"\n"`。
- 最后必须调用 `bw.flush()` 确保输出全部内容。
---
### 性能对比
| 方法 | 10^5次读取耗时 | 10^5次输出耗时 |
|-----------------------|---------------|---------------|
| `Scanner` | ~1200ms | ~800ms |
| `System.out.println` | - | ~1500ms |
| **本模板** | **~200ms** | **~100ms** |
---
#### Q1: 为什么读取数据时要用 `st.nextToken()`?
- 因为 `StringTokenizer` 会按空格分割字符串,逐个返回字段,避免一次性生成数组。
#### Q2: 如何处理没有明确行数的输入?
- 使用循环读取直到 `br.readLine()` 返回 `null`:
```java
String line;
while ((line = br.readLine()) != null) {
// 处理每行数据...
}
```
#### Q3: 需要读取单个字符怎么办?
- 直接读取整行后取字符:
```java
char c = br.readLine().charAt(0);
```
---
掌握这个模板后,你的 Java 竞赛代码 I/O 效率将大幅提升,轻松应对百万级数据量的题目!