在 Java 的 I/O 操作里,java.io.BufferedReader 类发挥着关键作用,它能为 Reader 实例提供缓冲功能。而缓冲机制能够显著提升 I/O 操作的速度。与普通方式一次仅从底层 Reader 读取一个字符不同,BufferedReader 会一次读取一大块数据(以数组形式)。这种读取方式效率更高,特别是在涉及磁盘访问或者处理大量数据时,优势尤为明显。
BufferedReader 和 BufferedInputStream 有相似之处,但也存在本质区别。二者虽都具备缓冲特性,不过 BufferedReader 专注于读取字符(也就是文本数据),而 BufferedInputStream 则用于读取原始字节数据。
由于 BufferedReader 类是 Reader 类的子类,所以在任何需要使用 Reader 的场景中,你都可以用 BufferedReader 来替代,从而借助其缓冲功能优化性能。
如果要为 Reader 实例添加缓冲功能,只需将其包装在 BufferedReader 中。如下所示:
package com.hxstrive.java_io; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.Reader; public class BufferedReaderExample { public static void main(String[] args) throws IOException { // 创建了一个 BufferedReader,缓冲区大小为 8192 try(Reader reader = new BufferedReader(new FileReader("data.txt"), 1024*8)) { // 一次读取一个字符,实际是从 BufferedReader 内部的字符数组中获取的 int val = reader.read(); while(val != -1) { System.out.print((char)val); val = reader.read(); } } } }
上述示例,创建了一个封装 FileReader 的 BufferedReader。BufferedReader 将从 FileReader 中读取一个字符块(通常是一个字符数组)。因此,从 read() 返回的每个字符都会从这个内部数组中返回。当数组完全读取完毕后,BufferedReader 会向数组中读取新的数据块。
你可以通过 BufferedReader 构造函数的参数去设置其内部缓冲区大小。如下:
Reader reader = new BufferedReader(new FileReader("data.txt"), 1024*8)
上述示例,将 BufferedReader 内部缓冲区大小设置为 8 KB。最好使用 1024 字节倍数的缓冲区大小。因为,这与硬盘等大多数内置缓冲区的效果最佳。
除了为 Reader 实例添加缓冲区外,BufferedReader 的行为与 Reader 非常相似。不过,BufferedReader 有一个额外的方法,即 readLine() 方法。如果需要一行一行地读取输入内容,这个方法会非常方便。下面是 BufferedReader 的 readLine() 方法示例:
package com.hxstrive.java_io; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class BufferedReaderExample2 { public static void main(String[] args) throws IOException { // 注意,readLine() 是 BufferedReader 中的方法,因此 // Reader 不能使用,必须将类型设置为 BufferedReader,如下: try(BufferedReader reader = new BufferedReader(new FileReader("data.txt"), 1024*8)) { String line = reader.readLine(); // 一次读取一行 while(null != line) { System.out.println(line); line = reader.readLine(); // 一次读取一行 } } } }
注意,上述示例中,readLine() 方法将返回从 BufferedReader 读取的文本行(在找到换行符之前的所有文本)。如果没有更多数据可从底层 Reader 读取,那么 BufferedReader 的 readLine() 方法将返回 null。
BufferedReader 的 read() 方法返回一个 int,其中包含下一个读取字符的字符值。如果 read() 方法返回 -1,则表示 BufferedReader 中已无数据可读,可以关闭了。
下面是一个从 BufferedReader 中读取所有字符的示例:
try(Reader reader = new BufferedReader(new FileReader("data.txt"), 1024*8)) { int val = reader.read(); // 读取一个字符 while(val != -1) { System.out.print((char)val); val = reader.read(); } }
注意,该示例代码首先从 BufferedReader 中读取一个字符,然后检查字符数值是否等于 -1。如果不等于,则处理该字符并继续读取,直到 BufferedReader 的 read() 方法返回 -1。
如前所述,BufferedReader 实际上是从底层 Reader 读取字符数组并逐个返回这些字符,而不是将每次 read() 调用都转发给底层 Reader。当读取完内部缓冲区中的所有字符后,BufferedReader 会尝试再次填充缓冲区,直到无法从底层 Reader 读取更多字符为止。
BufferedReader 类也有一个用来读取字符数组的 read(char[], offset, length) 方法,该方法将字符数组(char[])、起始偏移量(offset)和长度(length)作为参数。字符数组是 read() 方法读取字符存放的位置。偏移参数是 read() 方法读取的数据应从字符数组什么位置开始存放。长度参数是 read() 方法从偏移量开始向前读入的字符数。
下面是使用 BufferedReader 将字符数组读入字符数组的示例:
package com.hxstrive.java_io; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.Reader; public class BufferedReaderExample3 { public static void main(String[] args) throws IOException { try(Reader reader = new BufferedReader(new FileReader("data.txt"), 1024*8)) { // 字符数组 char[] chars = new char[32]; // 从 Reader 中读取字符数组 int len = reader.read(chars, 0, chars.length); while(len != -1) { // 输出到控制台 System.out.print(new String(chars, 0, len)); len = reader.read(chars, 0, chars.length); } } } }
注意,BufferedReader 的 read(char[], offset, length) 方法返回读入字符数组的字符数,如果 BufferedReader 中没有更多字符可读取,则返回 -1,例如,如果 BufferedReader 所连接的文件已到末尾。
BufferedReader 有一个名为 readLine() 的特殊读取方法,可从 BufferedReader 的内部缓冲区读取一整行文本。readLine() 方法返回一个字符串。如果 BufferedReader 中没有其他行可读,则 readLine() 方法返回 null。下面是一个使用 BufferedReader 逐行读取文本文件的示例:
BufferedReader bufferedReader = new BufferedReader(new FileReader("data.txt")); String line = bufferedReader.readLine(); // 读取一行 while(line != null) { System.out.println(line); line = bufferedReader.readLine(); // 读取一行 }
注意,一次读取一个字符数组要比从 Reader 中一次读取一个字符快。不过,由于 BufferedReader 已经进行了一些内部缓冲,因此两者之间的差异可能不会像不使用缓冲的 Reader 那样显著。
BufferedReader 类有一个名为 skip() 的方法,可用于跳过输入中不想读取的字符数。你可以将要跳过的字符数作为参数传递给 skip() 方法。下面是一个跳过 BufferedReader 中的字符的示例:
long charsSkipped = bufferedReader.skip(24);
上述示例,告诉 BufferedReader 跳过 BufferedReader 中的下一个 24 个字符。skip() 方法返回跳过的实际字符数。大多数情况下,跳过的字符数与您请求跳过的字符数相同,但如果 BufferedReader 中剩余的字符数少于您请求跳过的字符数,则返回的跳过字符数可能少于你请求跳过的字符数。
当你完成从 BufferedReader 中读取字符后,一定要记得关闭它。关闭 BufferedReader 也会关闭底层的 Reader 实例。
关闭 BufferedReader 仅需要调用其 close() 方法。下面是关闭 BufferedReader 的示例:
bufferedReader.close();
当然,你还可以使用 Java7 中引入的 try-with-resources 结构,让它自动帮你关闭 BufferedReader。如下:
Reader reader = new FileReader("data.txt"); try(BufferedReader bufferedReader = new BufferedReader(reader)){ String line = bufferedReader.readLine(); while(line != null) { System.out.print(line); line = bufferedReader.readLine(); } }
注意,第一个 FileReader 实例并没有在 try-with-resources 代码块中创建。这意味着 try-with-resources 块不会自动关闭 FileReader 实例。不过,当 BufferedReader 关闭时,它也会关闭它底层的 Reader 实例,因此 FileReader 实例会在 BufferedReader 关闭时被关闭。