算法题基础 : Java : BufferedReader、BufferedWriter 和 StringTokenizer 详解
BufferedReader
、BufferedWriter
和 StringTokenizer
详解
在 Java 中,处理输入输出和字符串分割时,BufferedReader
、BufferedWriter
和 StringTokenizer
是非常实用的工具类。它们在处理大量数据或频繁 IO 操作时表现出色,尤其适合算法竞赛、大数据处理等场景。下面进行更详细的介绍:
一、BufferedReader
:高效字符输入流
1. 核心原理
BufferedReader
是一个带缓冲区的字符输入流,它通过在内存中设置一块缓冲区(默认 8192 字符),一次性从底层输入流(如文件、键盘)读取大量数据到缓冲区,后续读取操作直接从缓冲区获取,减少了直接访问底层资源的次数,从而大幅提升读取效率。
对比 Scanner
:Scanner
功能丰富(支持正则解析、不同数据类型直接读取),但每次读取都可能触发底层 IO 操作,在处理 10^6
级数据时速度明显慢于 BufferedReader
。
2. 类定义与构造方法
// 类定义(继承自 Reader)
public class BufferedReader extends Reader {// 构造方法:接收一个底层输入流(如 InputStreamReader、FileReader)public BufferedReader(Reader in) { ... }// 构造方法:指定缓冲区大小public BufferedReader(Reader in, int sz) { ... }
}
常用底层流搭配:
- 读取键盘输入:
new BufferedReader(new InputStreamReader(System.in))
- 读取文件:
new BufferedReader(new FileReader("file.txt"))
3. 常用方法详解
方法签名 | 功能描述 | 示例 |
---|---|---|
String readLine() | 读取一整行文本(不含换行符),若已到流末尾则返回 null 。 | String line = br.readLine(); |
int read() | 读取单个字符(返回 ASCII 码,-1 表示结束)。 | int c = br.read(); |
int read(char[] cbuf) | 读取字符到数组 cbuf 中,返回实际读取的字符数(-1 表示结束)。 | char[] buf = new char[1024];<br>int len = br.read(buf); |
void close() | 关闭流,释放资源(必须调用,否则可能导致资源泄漏)。 | br.close(); |
long skip(long n) | 跳过 n 个字符(用于快速定位到需要读取的位置)。 | br.skip(100); // 跳过前100个字符 |
4. 异常处理
BufferedReader
的所有读取方法都会抛出 IOException
(受检异常),必须处理:
- 方式1:在方法上声明
throws IOException
(简单直接,适合算法题); - 方式2:使用
try-catch
捕获异常(适合生产环境代码)。
5. 适用场景
- 算法竞赛中处理大量输入(如
10^5
行数据); - 读取大文件内容;
- 需要高效按行读取文本的场景。
二、BufferedWriter
:高效字符输出流
1. 核心原理
BufferedWriter
与 BufferedReader
对应,是带缓冲区的字符输出流。它先将数据写入内存缓冲区,当缓冲区满或手动刷新时,才将数据一次性写入底层输出流(如控制台、文件),减少底层 IO 操作次数,提升写入效率。
对比 System.out.println
:System.out
本质是 PrintStream
,每次输出可能直接触发 IO 操作,而 BufferedWriter
通过缓冲机制显著提升大量数据的写入速度。
2. 类定义与构造方法
// 类定义(继承自 Writer)
public class BufferedWriter extends Writer {// 构造方法:接收一个底层输出流(如 OutputStreamWriter、FileWriter)public BufferedWriter(Writer out) { ... }// 构造方法:指定缓冲区大小public BufferedWriter(Writer out, int sz) { ... }
}
常用底层流搭配:
- 输出到控制台:
new BufferedWriter(new OutputStreamWriter(System.out))
- 输出到文件:
new BufferedWriter(new FileWriter("output.txt"))
3. 常用方法详解
方法签名 | 功能描述 | 示例 |
---|---|---|
void write(String str) | 写入字符串(不自动换行)。 | bw.write("hello"); |
void write(char[] cbuf) | 写入字符数组。 | char[] arr = {'a','b'}; bw.write(arr); |
void newLine() | 写入换行符(跨平台,自动适配 Windows 的 \r\n 或 Linux 的 \n )。 | bw.newLine(); |
void flush() | 强制刷新缓冲区,将数据立即写入底层流(必须调用,否则可能数据未输出)。 | bw.flush(); |
void close() | 关闭流(关闭前会自动刷新缓冲区)。 | bw.close(); |
4. 关键注意点
- 必须刷新或关闭:数据先存于缓冲区,若未调用
flush()
或close()
,可能导致数据未写入目标流(例如程序提前结束时); - 换行用
newLine()
:避免直接写\n
或\r\n
,newLine()
可保证跨平台兼容性; - 异常处理:所有方法抛出
IOException
,需同BufferedReader
一样处理。
5. 适用场景
- 算法竞赛中输出大量结果(如
10^6
个数字); - 写入大文件内容;
- 需要高效批量输出文本的场景。
三、StringTokenizer
:字符串分割工具
1. 核心功能
StringTokenizer
用于将一个字符串按指定分隔符分割成多个子串(称为“令牌”,Token),是处理简单分隔符场景的高效工具。
对比 String.split()
:
StringTokenizer
:不支持正则表达式,仅按固定字符分割,但效率更高,适合空格、逗号等简单分隔;String.split()
:支持正则表达式,功能更强大,但分割复杂字符串时效率较低。
2. 类定义与构造方法
// 类定义
public class StringTokenizer implements Enumeration<Object> {// 构造方法1:按默认分隔符(空格、制表符\t、换行符\n、回车符\r、换页符\f)分割public StringTokenizer(String str) { ... }// 构造方法2:按指定分隔符delim分割public StringTokenizer(String str, String delim) { ... }// 构造方法3:指定是否返回分隔符本身(true则分隔符也作为令牌)public StringTokenizer(String str, String delim, boolean returnDelims) { ... }
}
示例:
// 按默认分隔符分割(空格、制表符等)
StringTokenizer st1 = new StringTokenizer("a b\tc\nd");// 按逗号分割
StringTokenizer st2 = new StringTokenizer("1,2,3", ",");// 按冒号分割,且返回冒号本身
StringTokenizer st3 = new StringTokenizer("x:y:z", ":", true);
3. 常用方法详解
方法签名 | 功能描述 | 示例 |
---|---|---|
boolean hasMoreTokens() | 判断是否还有未处理的令牌(子串)。 | while (st.hasMoreTokens()) { ... } |
String nextToken() | 返回下一个令牌,若没有则抛出 NoSuchElementException 。 | String token = st.nextToken(); |
int countTokens() | 返回剩余的令牌数量(估计值,可能因分隔符设置变化)。 | int count = st.countTokens(); |
4. 典型用法(算法题输入分割)
算法题中常需读取一行空格分隔的数字,用 StringTokenizer
分割效率最高:
String line = "3 1 4 1 5";
StringTokenizer st = new StringTokenizer(line);
int n = Integer.parseInt(st.nextToken()); // 3
int a = Integer.parseInt(st.nextToken()); // 1
// 依次读取后续数字...
5. 局限性
- 不支持正则表达式(如无法用
\d+
分割数字); - 无法直接获取所有令牌(需循环调用
nextToken()
); - 不保留空令牌(例如
"a,,b"
按逗号分割时,会忽略中间的空字符串)。
四、三者协同使用示例(算法题场景)
下面是一个完整示例,展示如何用三者处理输入输出:
import java.io.*;
import java.util.StringTokenizer;public class Main {public static void main(String[] args) throws IOException {// 1. 初始化输入输出流BufferedReader br = new BufferedReader(new InputStreamReader(System.in));BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));// 2. 读取第一行:n和q(如区间加问题的输入)StringTokenizer st = new StringTokenizer(br.readLine());int n = Integer.parseInt(st.nextToken());int q = Integer.parseInt(st.nextToken());// 3. 处理q次操作int[] arr = new int[n + 1]; // 1-based索引for (int i = 0; i < q; i++) {st = new StringTokenizer(br.readLine());int li = Integer.parseInt(st.nextToken());int ri = Integer.parseInt(st.nextToken());int vi = Integer.parseInt(st.nextToken());// 区间内vi的倍数加1(核心逻辑)for (int j = li; j <= ri; j++) {if (j % vi == 0) {arr[j]++;}}}// 4. 输出结果for (int i = 1; i <= n; i++) {bw.write(arr[i] + "");}// 5. 释放资源bw.flush(); // 确保数据输出br.close();bw.close();}
}
总结
类 | 核心优势 | 典型应用场景 | 性能特点 |
---|---|---|---|
BufferedReader | 高效读取文本,减少IO次数 | 算法题大量输入、大文件读取 | 速度远快于 Scanner |
BufferedWriter | 高效写入文本,缓冲减少IO次数 | 算法题大量输出、大文件写入 | 速度远快于 System.out |
StringTokenizer | 简单分隔符分割字符串,效率高 | 空格/逗号分隔的输入解析(如算法题) | 速度快于 String.split() (简单场景) |
掌握这三个类的使用,能显著提升 Java 程序处理输入输出的效率,尤其在时间敏感的算法竞赛中,是提升程序性能的关键技巧。