Java Reader

更新于 2025-12-26

Jakob Jenkov 2019-08-27

Java 的 Reader 类(java.io.Reader)是 Java IO API 中所有 Reader 子类的基类。与 Java InputStream 不同,Reader 是基于字符(character-based)而非字节(byte-based)的。换句话说,Reader 用于读取文本(字符),而 InputStream 用于读取原始字节。

Reader 与数据源

Reader 通常连接到某种数据源,如文件、字符数组、网络套接字等。这一点在 Java IO 概述 中有更详细的说明。

Unicode 中的字符

如今,许多应用程序使用 UnicodeUTF-8 或 UTF-16)来存储文本数据。在 UTF-8 中,一个字符可能需要一个或多个字节表示;而在 UTF-16 中,每个字符通常占用两个字节。因此,在读取文本数据时,单个字节未必对应一个完整的字符。如果你通过 InputStream 逐字节读取 UTF-8 数据并试图将每个字节转换为 char,结果可能不是你期望的文本。

为了解决这个问题,Java 提供了 Reader 类。Reader 能够将字节解码为字符。你需要在实例化 Reader(实际上是其子类)时指定要使用的字符集。

Java Reader 的子类

通常你不会直接使用 Reader,而是使用它的某个子类。Java IO 提供了多种 Reader 子类,包括:

下面是一个创建 FileReaderReader 的子类)的示例:

Reader reader = new FileReader("/path/to/file/thefile.txt");

从 Reader 中读取单个字符

Readerread() 方法返回一个 int,其中包含下一个读取字符的 char 值。如果 read() 返回 -1,表示没有更多数据可读,此时可以关闭 Reader。注意:这里指的是 int 类型的 -1,而不是 bytechar 类型的 -1。

以下是从 Reader 中读取所有字符的示例:

Reader reader = new FileReader("/path/to/file/thefile.txt");
int theCharNum = reader.read();
while(theCharNum != -1) {
    char theChar = (char) theCharNum;
    System.out.print(theChar);
    theCharNum = reader.read();
}

注意:代码首先读取一个字符,并检查其数值是否等于 -1。如果不是,则处理该字符并继续读取,直到 read() 返回 -1。

从 Reader 中读取字符数组

Reader 还提供了一个 read(char[], offset, length) 方法,它接受一个字符数组、起始偏移量和要读取的字符数量作为参数。字符将被读入该数组中从 offset 开始的位置,最多读取 length 个字符。

示例:

Reader reader = new FileReader("/path/to/file/thefile.txt");
char[] theChars = new char[128];
int charsRead = reader.read(theChars, 0, theChars.length);
while(charsRead != -1) {
    System.out.println(new String(theChars, 0, charsRead));
    charsRead = reader.read(theChars, 0, theChars.length);
}

该方法返回实际读入数组的字符数,若已到达数据末尾则返回 -1。

读取性能

一次读取一个字符数组比逐个字符读取要快得多,性能提升可达 10 倍甚至更多。

具体提速效果取决于字符数组大小、操作系统、硬件等因素。通常建议使用至少 8KB 的缓冲区。但超过系统底层缓存能力后,再增大数组也不会带来明显提升。你可能需要通过实验测量不同缓冲区大小下的读取性能,以找到最优值。

通过 BufferedReader 实现透明缓冲

你可以使用 Java BufferedReaderReader 进行包装,从而自动实现高效缓冲。BufferedReader 会从底层 Reader 一次性读取一大块字符到内部缓冲区,然后你可以逐个字符地从 BufferedReader 读取,同时仍能获得批量读取带来的性能优势。

示例:

Reader input = new BufferedReader(
    new FileReader("c:\\data\\input-file.txt"),
    1024 * 1024 /* 缓冲区大小 */
);

注意:BufferedReader 本身也是 Reader 的子类,可以在任何需要 Reader 的地方使用。

跳过字符

Reader 提供了 skip(long n) 方法,用于跳过输入流中的若干字符。参数 n 表示要跳过的字符数。该方法返回实际跳过的字符数量(可能小于请求的数量,例如当剩余字符不足时)。

示例:

long charsSkipped = reader.skip(24);

关闭 Reader

读取完成后,应调用 close() 方法关闭 Reader

reader.close();

或者,使用 Java 7 引入的 try-with-resources 语法自动关闭资源:

try(Reader reader = new FileReader("/path/to/file/thefile.txt")) {
    int data = reader.read();
    while(data != -1) {
        System.out.print((char) data);
        data = reader.read();
    }
}

注意:这里不再需要显式调用 close(),try-with-resources 会自动处理。