ACM模式Java输入输出处理完全指南

难度: 基础到进阶

标签: ACM, Java, 输入输出, 算法竞赛, 编程技巧

适用场景: 算法竞赛、笔试面试、在线编程平台

📝 概述

ACM模式是算法竞赛中常见的编程模式,与LeetCode的函数式编程不同,ACM模式需要手动处理输入输出。本指南涵盖Java中各种常见的输入输出处理模式。

💭 基础知识

初步理解

ACM模式要求程序能够:

  1. 读取多组测试数据
  2. 处理不同格式的输入
  3. 按照指定格式输出结果
  4. 处理输入结束条件

常用类和方法

  • Scanner类:最常用的输入处理类
  • BufferedReader类:高效读取大量数据
  • StringTokenizer类:字符串分割
  • System.out.print()System.out.println():输出

🎯 常见输入输出模式

模式一:固定数量输入

场景描述
输入第一行是测试用例数量n,后面跟着n行数据。

输入样例

1
2
3
4
3
1 2
3 4
5 6

输出样例

1
2
3
3
7
11

代码实现(使用Scanner):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 读取测试用例数量

for (int i = 0; i < n; i++) {
int a = sc.nextInt();
int b = sc.nextInt();
System.out.println(a + b); // 输出每行的和
}

sc.close();
}
}

模式二:直到特定值结束

场景描述
输入多组数据,直到遇到特定值(如0)时结束。

输入样例

1
2
3
4
5 3
10 20
0 0
15 25

输出样例

1
2
8
30

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);

while (sc.hasNextInt()) {
int a = sc.nextInt();
int b = sc.nextInt();

if (a == 0 && b == 0) {
break; // 遇到0 0时结束
}

System.out.println(a + b);
}

sc.close();
}
}

更多测试用例

  • 立即结束

    1
    0 0

    输出:(无输出)

  • 负数处理

    1
    2
    3
    -5 10
    3 -7
    0 0

    输出:

    1
    2
    5
    -4
  • 单组数据

    1
    2
    100 200
    0 0

    输出:

    1
    300

模式三:直到文件末尾

场景描述
输入多组数据,直到文件末尾(EOF)。

输入样例

1
2
3
1 2
3 4
5 6

输出样例

1
2
3
3
7
11

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);

while (sc.hasNextInt()) {
int a = sc.nextInt();
int b = sc.nextInt();
System.out.println(a + b);
}

sc.close();
}
}

更多测试用例

  • 单行输入

    1
    42 58

    输出:

    1
    100
  • 多行混合数据

    1
    2
    3
    4
    10 20
    30 40
    50 60
    70 80

    输出:

    1
    2
    3
    4
    30
    70
    110
    150
  • 实际应用场景(文件重定向):

    1
    2
    # 将数据保存在input.txt文件中
    java Main < input.txt

模式四:字符串处理

场景描述
处理包含空格的字符串输入。

输入样例

1
2
3
4
3
Hello world Java programming
ACM algorithm competition
Data structures and algorithms

输出样例

1
2
3
Word count: 4
Word count: 3
Word count: 4

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
sc.nextLine(); // 消费掉换行符

for (int i = 0; i < n; i++) {
String line = sc.nextLine(); // 读取整行
String[] words = line.split(" "); // 按空格分割
System.out.println("Word count: " + words.length);
}

sc.close();
}
}

⚠️ 重要注意事项:Scanner的nextLine()陷阱

Q1: 为什么 nextLine() 在 nextInt() 或 next() 之后不生效?

nextInt()next() 不会读取换行符,导致后续的 nextLine() 直接读取到空行。

解决方法:在 nextInt()next() 后加一个 nextLine() 来消耗换行符。

错误示例

1
2
3
4
5
6
Scanner sc = new Scanner(System.in);
System.out.print("输入数字:");
int num = sc.nextInt(); // 读取数字,但不读取换行符
System.out.print("输入字符串:");
String str = sc.nextLine(); // 直接读取换行符,导致 str 为空
System.out.println("数字:" + num + ",字符串:" + str);

输入

1
2
123
Hello

输出

1
数字:123,字符串:

nextLine() 读取了 nextInt() 留下的换行符,导致 str 为空。

修正方法

1
2
3
4
5
6
7
Scanner sc = new Scanner(System.in);
System.out.print("输入数字:");
int num = sc.nextInt();
sc.nextLine(); // 消耗换行符
System.out.print("输入字符串:");
String str = sc.nextLine(); // 正确读取
System.out.println("数字:" + num + ",字符串:" + str);

输出

1
数字:123,字符串:Hello

更多测试用例

  • 多空格处理(改进版本):

    1
    2
    // 改进版本:使用正则表达式处理多个空格
    String[] words = line.split("\\s+");
  • 空字符串测试

    1
    2
    3
    2

    Hello world

    输出:

    1
    2
    Word count: 1
    Word count: 2
  • 特殊字符处理

    1
    2
    3
    2
    Hello, world!
    Java-programming@2025

    输出:

    1
    2
    Word count: 2
    Word count: 1

模式五:高性能输入(BufferedReader)

场景描述
处理大量数据时,需要更高效的输入方式。

输入样例

1
2
3
4
5
4
100 200
50 75
300 400
25 35

输出样例

1
2
3
4
300
125
700
60

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.*;
import java.util.StringTokenizer;

public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());

int n = Integer.parseInt(st.nextToken());

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

br.close();
}
}

性能对比测试

  • 大数据量测试(10万行):
    1
    2
    3
    4
    5
    6
    100000
    1 2
    3 4
    5 6
    ...
    [继续99997行]
    性能数据
    • Scanner: 约500-800ms
    • BufferedReader: 约100-200ms
    • 性能提升:4-5倍

适用场景

  • n > 10000时推荐使用
  • 时间限制严格的竞赛题目
  • 需要处理大规模输入数据

模式六:二维数组输入

场景描述
输入二维数组或矩阵。

输入样例

1
2
3
4
3 4
1 2 3 4
5 6 7 8
9 10 11 12

输出样例

1
Matrix sum: 78

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int rows = sc.nextInt();
int cols = sc.nextInt();

int[][] matrix = new int[rows][cols];

for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = sc.nextInt();
}
}

// 处理矩阵:计算所有元素的和
int sum = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
sum += matrix[i][j];
}
}
System.out.println("Matrix sum: " + sum);

sc.close();
}
}

动态数组实现(使用ArrayList)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.ArrayList;

public class DynamicMatrix {
public static void main(String[] args) {
int rows = 2, cols = 3;
ArrayList<ArrayList<Integer>> matrix = new ArrayList<>();

// 为每行分配内存
for (int i = 0; i < rows; ++i) {
ArrayList<Integer> row = new ArrayList<>();
for (int j = 0; j < cols; ++j) {
row.add(0); // 初始化为0
}
matrix.add(row);
}

// 初始化矩阵
matrix.get(0).set(0, 1);
matrix.get(0).set(1, 2);
matrix.get(0).set(2, 3);
matrix.get(1).set(0, 4);
matrix.get(1).set(1, 5);
matrix.get(1).set(2, 6);

// 访问矩阵元素
System.out.println(matrix.get(1).get(2)); // 输出6


// 输出矩阵的所有元素
for (int i = 0; i < matrix.size(); ++i) {
for (int j = 0; j < matrix.get(i).size(); ++j) {
System.out.print(matrix.get(i).get(j) + " ");
}
System.out.println();
}
// 释放内存(Java有垃圾回收机制,无需手动释放)
}
}

更多测试用例

  • 正方形矩阵

    1
    2
    3
    4
    3 3
    1 2 3
    4 5 6
    7 8 9

    输出:

    1
    Matrix sum: 45
  • 单行矩阵

    1
    2
    1 5
    10 20 30 40 50

    输出:

    1
    Matrix sum: 150
  • 单列矩阵

    1
    2
    3
    4
    5
    4 1
    1
    2
    3
    4

    输出:

    1
    Matrix sum: 10
  • 边界情况(1×1矩阵):

    1
    2
    1 1
    42

    输出:

    1
    Matrix sum: 42

模式七:不定长数组输入

场景描述
每行输入不定数量的数字。

输入样例

1
2
3
4
1 2 3 4 5
10 20 30 40
100

输出样例

1
2
3
15
100
100

代码实现

方法1:使用 Scanner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);

while (sc.hasNextLine()) {
String line = sc.nextLine();
if (line.trim().isEmpty()) {
continue;
}

String[] numbers = line.split("\\s+"); // 使用\\s+处理多个空格
int sum = 0;
for (String num : numbers) {
if (!num.trim().isEmpty()) {
sum += Integer.parseInt(num);
}
}
System.out.println(sum);
}

sc.close();
}
}

方法2:使用 Scanner嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String line;
long sum;

while (sc.hasNextLine()) { // 持续读取每一行,直到输入结束
line = sc.nextLine();
if (line.trim().isEmpty()) { // 如果遇到空行,跳过
continue;
}

Scanner lineScanner = new Scanner(line); // 创建新的Scanner读取每行的数据
sum = 0;
while (lineScanner.hasNextLong()) { // 逐个读取这一行的整数
long num = lineScanner.nextLong();
sum += num; // 将读取的整数累加到 sum 中
}
System.out.println(sum); // 输出这一行所有整数的和
lineScanner.close();
}
sc.close(); // 关闭Scanner
}
}

更多测试用例

  • 多空格处理

    1
    5   10    15      20

    输出:

    1
    50
  • 单行单数

    1
    42

    输出:

    1
    42
  • 负数处理

    1
    -10 20 -5 15

    输出:

    1
    20
  • 空行结束

    1
    2
    3
    1 2 3
    4 5 6

    输出:

    1
    2
    6
    15

模式八:多组测试用例(每组不同格式)

场景描述
复杂的输入格式,每组测试用例可能包含不同类型的数据。

输入样例

1
2
3
4
5
6
7
8
9
10
3
5
1 2 3 4 5
sum 0
4
10 20 30 40
max 0
6
1 2 3 4 5 3
count 3

输出样例

1
2
3
Case #1: 15
Case #2: 40
Case #3: 2

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt(); // 测试用例数量

for (int caseNum = 1; caseNum <= t; caseNum++) {
int n = sc.nextInt(); // 数组大小
int[] arr = new int[n];

for (int i = 0; i < n; i++) {
arr[i] = sc.nextInt();
}

String operation = sc.next(); // 操作类型
int k = sc.nextInt(); // 操作参数

// 执行操作
int result = processArray(arr, operation, k);

System.out.println("Case #" + caseNum + ": " + result);
}

sc.close();
}

private static int processArray(int[] arr, String operation, int k) {
switch (operation.toLowerCase()) {
case "sum":
return java.util.Arrays.stream(arr).sum();
case "max":
return java.util.Arrays.stream(arr).max().orElse(0);
case "count":
return (int) java.util.Arrays.stream(arr).filter(x -> x == k).count();
case "find":
for (int i = 0; i < arr.length; i++) {
if (arr[i] == k) return i;
}
return -1;
default:
return 0;
}
}
}

更多测试用例

  • 查找操作

    1
    2
    3
    4
    5
    6
    7
    2
    5
    10 20 30 40 50
    find 30
    3
    1 2 3
    find 5

    输出:

    1
    2
    Case #1: 2
    Case #2: -1
  • 单元素数组

    1
    2
    3
    4
    1
    1
    42
    sum 0

    输出:

    1
    Case #1: 42
  • 边界测试

    1
    2
    3
    4
    1
    3
    -5 0 5
    max 0

    输出:

    1
    Case #1: 5

🚀 关键要点

核心概念

  1. 输入流处理:理解如何读取不同类型的输入数据
  2. 循环控制:掌握各种输入结束条件的处理
  3. 字符串分割:熟练使用split()和StringTokenizer
  4. 异常处理:合理处理输入异常

常见陷阱

  1. 换行符处理:nextInt()后使用nextLine()需要特别注意
  2. 空格处理:字符串输入中的空格分割问题
  3. EOF判断:正确处理文件结束条件
  4. 性能问题:大量数据时选择合适的输入方式

性能优化建议

  • 小数据量:使用Scanner,代码简洁
  • 大数据量:使用BufferedReader + StringTokenizer
  • 超大数组:考虑使用快速输入输出类

📚 实战练习

练习题1:A + B Problem

描述:计算两个数的和,直到文件结束。
输入:多行,每行两个整数
输出:每行输出对应的和

练习题2:字符串统计

描述:统计输入字符串中各个字符的出现次数。
输入:第一行是测试用例数量,后面是字符串
输出:每个字符串的字符统计结果

练习题3:矩阵运算

描述:实现矩阵的转置操作。
输入:矩阵的行数和列数,然后是矩阵元素
输出:转置后的矩阵

🏷️ 相关标签

  • ACM
  • Java
  • 输入输出
  • 算法竞赛
  • 编程技巧
  • 笔试面试

📖 参考资料


最后更新:2025-01-15