“字符输入流”在 Java 中对应的顶层抽象类是 java.io.Reader。这两个 read 方法就是 Reader 类中定义的(或其子类如 FileReader, InputStreamReader 实现的)。
int read() (空参 read)这个方法是字符流中最基础的读取方法。
功能: 尝试从输入流中读取单个字符。
返回值 (int):
这是最容易让人困惑的一点:为什么读取一个 char,却返回一个 int?
char 来获取原始字符。1。关键点: 返回 int 而不是 char 是为了能有一个“带外值”(out-of-band value)。char 类型无法表示一个“不是字符”的信号。而 int 可以表示 0-65535 范围内的所有字符,同时还能用 -1 这个不在 char 范围内的值来明确表示“流已结束 (End-of-Stream, EOS)”。
<aside> 💡
在 Java 中,char 类型是一个 16 位的无符号(unsigned)数据类型。
\\u0000 (即 0) 到 \\uffff (即 65,535)。read() 方法的设计者需要解决一个问题:
如果 read() 方法的返回值被设计为 char,它就无法区分“文件末尾”和“一个值为 65,535(或任何其他值)的有效字符”。
因此,选择一个范围更广并且有符号的类型—— int ——是必然的解决方案:
read() 返回 0 到 65,535 之间的值时: 这代表一个有效的字符 Unicode 码值。我们可以安全地将它强制转换为 char。read() 返回 -1 时: 这是一个“带外值”(out-of-band value),它不在 char 的合法范围内,被专门用来表示“流已结束”。
</aside>如何使用: 你必须在一个循环中调用它,并且每次都要检查返回值是否为 -1。
Reader reader = null;
try {
reader = new FileReader("example.txt");
int charCode; // 必须用 int 来接收
// 经典循环:先读取,再在循环条件中检查是否为 -1
while ((charCode = reader.read()) != -1) {
// 如果不为 -1,说明是有效字符,可以安全转换
char character = (char) charCode;
System.out.print(character);
}
// 当 read() 返回 -1 时,循环终止
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
性能:非常低效。 每调用一次 read(),(在没有缓冲的情况下)都可能涉及一次昂贵的系统调用 (System Call) 来从操作系统获取一个字符。如果你要读取一个大文件,这会造成巨大的性能瓶P颈。
类比: 用筷子一粒一粒地夹米饭。
int read(char[] cbuf) / int read(char[] cbuf, int off, int len) (有参 read)这是 read 方法的重载版本,也是推荐使用的读取方式。它们实现了“缓冲读取”的核心逻辑。
我将重点讲解三个参数的版本 read(char[] cbuf, int off, int len),因为 read(char[] cbuf) 只是 read(cbuf, 0, cbuf.length) 的一个便捷写法。
功能:
尝试从输入流中读取一批字符,并将它们存入你提供的字符数组(缓冲区)cbuf 中。
参数:
char[] cbuf: 这是一个“缓冲区”,是你预先分配好的一块内存,用来存放即将读到的数据。int off (Offset): 偏移量。指定从 cbuf 数组的哪个索引位置开始存放数据。int len (Length): 长度。指定最多想要读取多少个字符。返回值 (int):
len 之间(包含 1 和 len)。len? 比如,你指定 len 为 1024,但流中只剩下 500 个字符了,它就会读取 500 个字符并返回 500。read() 一样,返回 1。注意: 只有在尚未读取任何字符就遇到了流末尾时,才会返回 -1。如何使用: 你同样需要一个循环,但这次是基于一个“缓冲区”来批量处理。
Reader reader = null;
try {
reader = new FileReader("example.txt");
// 1. 创建一个缓冲区数组
char[] buffer = new char[1024]; // 比如一次最多读 1024 个字符
int charsRead; // 用于接收实际读到的字符数
// 2. 经典循环
// 尝试填满 buffer (从索引0开始, 最多读 buffer.length 个)
while ((charsRead = reader.read(buffer, 0, buffer.length)) != -1) {
// 3. 处理读到的数据
// 重点:只处理 buffer 中从 0 到 charsRead-1 的这部分
// 因为这才是本次真正读到的有效数据
String data = new String(buffer, 0, charsRead);
System.out.print(data);
}
// 当 read() 返回 -1 时,循环终止
} catch (IOException e) {
e.printStackTrace();
} finally {
// ... (省略关闭流的代码)
}
性能:高效。 你一次性从操作系统请求一大块数据(比如 1024 个字符),系统调用次数大大减少。这是处理 I/O 的标准做法。
类比: 用一个大碗去盛米饭。