从 TLE 到极速通过:Java 算法竞赛大数据量输入优化(BufferedReader + StringTokenizer 详解)

📅 2026/7/9 3:26:50
从 TLE 到极速通过:Java 算法竞赛大数据量输入优化(BufferedReader + StringTokenizer 详解)
从 TLE 到极速通过Java 算法竞赛大数据量输入优化BufferedReader StringTokenizer 详解引言为什么你的 Java 算法逻辑对了却依然超时很多刚从 LeetCode 转战到蓝桥杯、洛谷、ACMCodeforces/Nowcoder等平台的 Java 选手经常会遇到一个令人沮丧的问题“我的算法时间复杂度明明是O ( N ) O(N)O(N)逻辑也完全没问题为什么一提交就显示 TLE超时甚至 MLE内存超限”其实问题往往不在你的算法逻辑上而是在Java 的输入输出I/O性能上。当题目给出的输入数据量达到10 5 10^5105到10 6 10^6106级别时传统的输入方式往往会成为程序的性能瓶颈。本文将深度拆解 Java 输入的底层隐患并带你掌握算法竞赛中不可或缺的黄金搭档——BufferedReader与StringTokenizer。避坑指南为什么 Scanner 和 split() 是性能杀手1. Scanner便利背后的沉重代价在平时练习中我们最习惯使用Scanner sc new Scanner(System.in);。然而Scanner的底层实现非常复杂它内部使用了大量的正则表达式来匹配不同类型的数据如nextInt()、nextDouble()。正则表达式的解析和匹配是非常消耗 CPU 资源的导致其运行速度极慢。在大数据量超过10 5 10^5105的情况下光是读入数据就可能耗尽题目的时间限制。2. split( “)隐形的内存刺客”为了加速有些同学换成了BufferedReader但习惯用split来切割字符串// 这是一个常见的性能陷阱String[]arrbr.readLine().split( );intaInteger.parseInt(arr[0]);intbInteger.parseInt(arr[1]);这样做同样非常危险split方法会在底层创建大量的String[]数组和全新的String子对象。如果在多组测试数据或循环中使用split会在短时间内产生海量的垃圾对象频繁触发 Java 的GC垃圾回收机制。这不仅会造成程序卡顿导致TLE甚至可能直接因为内存占满而导致MLE。如果输入数据中包含连续的空格split还会切出空字符串从而导致Integer.parseInt抛出异常。黄金搭档BufferedReader StringTokenizer为了解决上述问题我们需要引入一套更轻量、更高性能的输入方案。1. BufferedReader高效的数据搬运工BufferedReader的核心在于**“缓冲Buffer”**。它不会每次需要一个字符就去和操作系统交互而是提前在内存中开辟一个大缓冲区默认 8KB一次性读入大量字符后续的读取直接在内存缓冲区中完成从而极大地减少了系统调用的开销。2. StringTokenizer轻量级的指针切割器不同于split会生成庞大的对象数组StringTokenizer采用了类似于指针移动的轻量级实现它直接在原字符串上定位只在你需要时通过nextToken()像剥糖果一样依次取出下一个词。它会自动过滤多余的空格、制表符Tab和换行符容错率极高。它不需要频繁创建大量字符串垃圾内存消耗极小。实战演练代码的进化史我们通过一个简单的场景来看看代码是如何一步步优化的。场景读入一个N × M N \times MN×M的矩阵并进行数据处理。进化前Scanner 方案最慢易超时ScannerscnewScanner(System.in);intnsc.nextInt();intmsc.nextInt();int[][]matrixnewint[n][m];for(inti0;in;i){for(intj0;jm;j){matrix[i][j]sc.nextInt();}}进化中BufferedReader split 方案有安全隐患易 MLEBufferedReaderbrnewBufferedReader(newInputStreamReader(System.in));String[]firstLinebr.readLine().split( );intnInteger.parseInt(firstLine[0]);intmInteger.parseInt(firstLine[1]);int[][]matrixnewint[n][m];for(inti0;in;i){String[]rowbr.readLine().split( );for(intj0;jm;j){matrix[i][j]Integer.parseInt(row[j]);// 频繁创建数组GC压力大}}终极形态BufferedReader StringTokenizer极速且安全BufferedReaderbrnewBufferedReader(newInputStreamReader(System.in));StringTokenizerstnewStringTokenizer(br.readLine());intnInteger.parseInt(st.nextToken());intmInteger.parseInt(st.nextToken());int[][]matrixnewint[n][m];for(inti0;in;i){// 每一行重新初始化一个 Tokenizer读取速度极快stnewStringTokenizer(br.readLine());for(intj0;jm;j){matrix[i][j]Integer.parseInt(st.nextToken());}}算法竞赛专属Java 极速输入输出Fast I/O标准模板在比赛中为了避免每次都手写StringTokenizer的判断我们可以将其封装成一个类似Scanner用法的工具类FastReader或直接命名为Read。建议将以下代码保存为你的个人常用模板在遇到大数据量题目时直接套用importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.util.StringTokenizer;publicclassMain{// // 核心FastReader 快速读入模板// staticclassFastReader{BufferedReaderbr;StringTokenizerst;publicFastReader(){brnewBufferedReader(newInputStreamReader(System.in));}// 核心读取方法当前行读完时自动读取下一行Stringnext(){while(stnull||!st.hasMoreTokens()){try{Stringlinebr.readLine();if(linenull){returnnull;// 读到文件末尾}stnewStringTokenizer(line);}catch(IOExceptione){e.printStackTrace();}}returnst.nextToken();}// 快捷读取整数intnextInt(){returnInteger.parseInt(next());}// 快捷读取长整数longnextLong(){returnLong.parseLong(next());}// 快捷读取浮点数doublenextDouble(){returnDouble.parseDouble(next());}}publicstaticvoidmain(String[]args){// 实例化快读类FastReaderscnewFastReader();// 像使用 Scanner 一样调用但速度提升数十倍intnsc.nextInt();longmsc.nextLong();System.out.println(读入成功n, m);}}总结与建议在算法竞赛中选择合适的工具往往能让你事半功倍数据量较小 10 4 10^4104可以使用Scanner图个方便和快捷。数据量中等10 4 ∼ 10 5 10^4 \sim 10^5104∼105推荐换用BufferedReader确保不会超时。大数据量 10 5 10^5105或多组测试数据必须使用BufferedReaderStringTokenizer或使用上述封装好的FastReader模板保障程序的稳定运行。希望这篇博客能帮你在后续的刷题和比赛中避开 I/O 的深坑顺利通过所有大测试点如果你有任何关于 Java 优化的问题欢迎在评论区一起讨论交流。大数据量 10 5 10^5105或多组测试数据必须使用BufferedReaderStringTokenizer或使用上述封装好的FastReader模板保障程序的稳定运行。希望这篇博客能帮你在后续的刷题和比赛中避开 I/O 的深坑顺利通过所有大测试点如果你有任何关于 Java 优化的问题欢迎在评论区一起讨论交流。