Java 大数据量输入输出优化方案详解:从 Scanner 到手写快读(其中方案全部使用计数排序)
前言
在算法竞赛或处理大规模数据的工程场景中,Java 程序常因输入输出(IO)效率低下而超时。尤其当数据量达到数十万甚至数百万级别时,使用默认的 Scanner 和 System.out 往往无法满足性能要求。本文将介绍几种 Java 中高效处理大量整数输入输出的方法,并结合具体的题目来给出真实的测试
问题背景
该题目为洛谷的P1271 【深基9.例1】选举学生会,是一道典型的排序加大数据量读写的题目,题目看似不难,但使用java语言进行做题时,如若使用传统
Scanner和sout读写将导致判题不通过,因此要考虑使用高效的io或手写快读方式进行读写

方案一:基础方法 ------ Scanner(不推荐)
java
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
// ...
漫画解析:

缺点分析:
Scanner内部使用正则表达式解析 token,开销大;- 每次调用
nextInt()都涉及字符串创建与解析; - 未使用高效缓冲机制,频繁系统调用导致性能瓶颈;
- 在 2e6 数据量下,运行时间通常超过 1500ms,极易超时。
测评结果如下:

可以很明显的看到在进行大数据量的读取时,空间复杂度直接不通过本题
结论:仅适用于教学或小规模数据,竞赛中应避免使用。
方案二:BufferedReader + StringTokenizer(常用方案)
java
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
int m = Integer.parseInt(st.nextToken());
漫画解析:

优势:
BufferedReader提供字符流缓冲,大幅减少系统调用次数;StringTokenizer快速分割字符串,无正则开销;- 配合
Integer.parseInt()解析整数,效率显著优于Scanner。
局限性:
- 仍需将子串(如
"123")转换为String对象,再解析为整数; - 每次
parseInt涉及字符遍历与数值计算,存在冗余内存分配; - 在极限数据下仍有优化空间。
适用场景: 大多数 OJ 平台的标准解法,稳定且易于理解。
测评结果如下:

可以看到相比于方案一,时间复杂度明显降低,空间复杂度也降了下来
方案三:BufferedReader + StreamTokenizer(更优方案)
java
StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
st.nextToken();
int n = (int) st.nval;
漫画解析:

核心优势:
StreamTokenizer在读取过程中直接解析数值,无需中间字符串;- 数值结果直接存入
nval(double 类型),避免parseInt开销; - 自动跳过空白字符(空格、换行等),兼容多行或单行输入;
- 内存分配极少,GC 压力小。
注意事项:
nval为double类型,需强制转为int,但在题目保证整数范围内安全;- 默认支持负数,通用性强。
性能表现: 在 2e6 整数输入下,通常耗时 400~600ms,远优于前两种方法。
适用场景: 需要高吞吐整数读取的竞赛题,代码简洁且高效。
测评结果如下:

可以看到相比于方案二,时间复杂度和空间复杂度进一步的降低
方案四:手写快读 + PrintWriter(极致优化)
java
static int readInt() throws IOException {
int x = 0, c;
boolean neg = false;
while ((c = System.in.read()) <= ' ');
if (c == '-') { neg = true; c = System.in.read(); }
while (c >= '0' && c <= '9') {
x = x * 10 + (c - '0');
c = System.in.read();
}
return neg ? -x : x;
}
漫画解析:

优化原理:
- 直接调用
System.in.read()读取字节,绕过字符流封装; - 手动解析数字,无任何对象创建(零
String、零Token); - 跳过所有空白字符,自动处理换行与空格;
- 输出使用
PrintWriter包裹BufferedOutputStream,确保写入缓冲。
性能优势:
- 比
StreamTokenizer再快 20%~30%; - 在 2e6 数据下可控制在 300~500ms 内;
- 极低内存占用,适合内存敏感环境。
适用场景: 数据量极大(≥1e6)、时限紧张的压线题目;追求极致性能的选手首选。
测评结果如下:

提交测评后可以看到虽然相比方案三耗时相差无几,但是明显更加节省空间
输出优化:统一使用 PrintWriter
无论采用哪种输入方式,输出部分都应避免直接使用 System.out.print。原因如下:
System.out是未缓冲的PrintStream,每次调用都可能触发系统写操作;- 使用
PrintWriter包裹BufferedOutputStream可将输出缓存,最后一次性刷新。
推荐写法:
java
// 创建 PrintWriter 包装 BufferedOutputStream,提升输出效率
// - System.out 是字节流,直接打印频繁时效率低
// - BufferedOutputStream 提供缓冲区,减少系统调用次数
// - PrintWriter 提供方便的 print/println 方法,并支持自动刷新(可选)
PrintWriter pw = new PrintWriter(new BufferedOutputStream(System.out));
// 输出一个整数 x(或其他基本类型)
// pw.print(x) 会将 x 转为字符串并写入缓冲区
pw.print(x);
// 输出一个空格:使用 write(' ') 比 print(' ') 更轻量
// - write(char) 直接写入单个字符(实际是写入其低 8 位字节)
// - print(char) 会经过更多方法调用和内部检查,开销略大
// 注意:write(int) 实际写的是字节,所以 ' '(ASCII 32)能正确输出空格
pw.write(' ');
// 强制将缓冲区内容刷出到控制台
// - 若不 flush,程序结束前可能看不到输出(尤其在 OJ 中可能导致 WA)
// - 通常在所有输出完成后 flush 一次即可,无需每次 print 都 flush
pw.flush();
// 关闭 PrintWriter(也会自动 flush 并关闭底层流)
// - 良好习惯:释放资源
// - 注意:关闭后不能再使用 pw,否则抛异常
pw.close();
漫画解析:

总结与建议
| 方法 | 时间复杂度(IO) | 代码复杂度 | 推荐指数 | 适用场景 |
|---|---|---|---|---|
| Scanner | 高 | 低 | ★☆☆☆☆ | 小数据、学习 |
| BufferedReader + StringTokenizer | 中 | 中 | ★★★★☆ | 一般竞赛题 |
| StreamTokenizer | 低 | 低 | ★★★★★ | 大整数输入,推荐默认使用 |
| 手写快读 | 极低 | 高 | ★★★★★ | 极限性能需求 |
实践建议:
- 日常练习可使用
StreamTokenizer,兼顾效率与简洁; - 遇到 1e6+ 数据且时限紧张时,启用手写快读;
- 输出务必使用
PrintWriter + BufferedOutputStream; - 不必过度纠结尾部空格问题,主流 OJ 均允许。
通过合理选择 IO 策略,即使是 Java 这类"慢语言",也能在大数据场景下高效运行。掌握这些技巧,将显著提升你在算法竞赛和工程实践中的竞争力。