源本科技 | 码上会

Java Reader

2026/01/30
46
0

学习目标

  • 理解 Reader 类在 Java I/O 体系中的定位与作用

  • 掌握 Reader 的核心方法及其使用场景

  • 能够通过子类(如 FileReader)读取文本文件

  • 了解 mark() / reset() 等高级功能的使用条件

  • 学会安全地管理字符流资源


什么是 Reader 类

Reader 是 Java 中用于读取字符流抽象基类,位于 java.io 包中。它专为处理 16 位 Unicode 字符而设计,适用于所有文本数据的读取操作。与字节流(InputStream)不同,Reader 及其子类能自动处理字符编码(如 UTF-8、GBK),避免乱码问题,是处理文本文件的首选。

类声明

public abstract class Reader implements Readable, Closeable
  • 实现 Readable 接口:支持向 CharBuffer 写入数据

  • 实现 Closeable 接口:提供 close() 方法释放系统资源


常见子类

子类

用途

FileReader

从文件读取字符(默认使用系统默认编码)

BufferedReader

带缓冲的字符输入流,支持按行读取(readLine()

InputStreamReader

将字节流(如 FileInputStream)转换为字符流,可指定编码

StringReader

从字符串中读取字符

实际开发中,通常不会直接实例化 Reader,而是使用其具体子类。


核心方法

方法

描述

int read()

读取单个字符,返回 int(0~65535),末尾返回 -1

int read(char[] cbuf)

将字符读入整个字符数组

abstract int read(char[] cbuf, int off, int len)

从偏移 off 开始,最多读取 len 个字符

int read(CharBuffer target)

读取字符到 CharBuffer(NIO 支持)

void close()

关闭流并释放资源(必须调用!)

boolean markSupported()

判断是否支持 mark() / reset()

void mark(int readAheadLimit)

标记当前位置(需子类支持)

void reset()

回退到最近的 mark 位置

long skip(long n)

跳过 n 个字符

boolean ready()

判断是否可立即读取(非阻塞)


基础示例

逐字符读取文件

以下程序演示如何使用 FileReaderReader 的子类)读取文本文件:

import java.io.*;

public class FileReaderExample {
    public static void main(String[] args) {
        // 确保当前目录下存在 example1.txt,内容如:
        // Hello welcome to Developer Community

        try (Reader reader = new FileReader("example1.txt")) {
            int data;
            while ((data = reader.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            System.err.println("读取文件时发生错误: " + e.getMessage());
        }
    }
}

输出:

Hello welcome to Developer Community

使用 try-with-resources 语法自动关闭 Reader,无需手动调用 close()


高级功能示例

mark、skip 与缓冲读取

以下程序展示 Reader 的多种方法协同工作:

import java.io.*;
import java.nio.CharBuffer;
import java.util.Arrays;

public class ReaderAdvancedExample {
    public static void main(String[] args) throws IOException {
        // 假设 file.txt 内容为:"ABCDEFGHIJKLMNOPQRSTUVWXYZ"

        try (Reader reader = new FileReader("file.txt")) {
            char[] buffer = new char[10];
            CharBuffer charBuffer = CharBuffer.wrap(buffer);

            // 检查是否支持 mark/reset
            if (reader.markSupported()) {
                reader.mark(100); // 标记当前位置,最多回退 100 个字符
                System.out.println("mark 功能受支持");
            }

            // 跳过前 5 个字符(A~E)
            reader.skip(5);

            // 读取接下来的 10 个字符(F~O)
            reader.read(buffer, 0, 10);
            System.out.println("读取的字符数组: " + Arrays.toString(buffer));

            // 继续读取到 CharBuffer(P~T)
            reader.read(charBuffer);
            System.out.println("CharBuffer 内容: " + Arrays.toString(charBuffer.array()));

            // 读取下一个字符(U)
            System.out.println("下一个字符: " + (char) reader.read());
        }
    }
}

假设 file.txt 内容为:

ABCDEFGHIJKLMNOPQRSTUVWXYZ

可能输出:

mark 功能受支持
读取的字符数组: [F, G, H, I, J, K, L, M, N, O]
CharBuffer 内容: [P, Q, R, S, T, K, L, M, N, O]
下一个字符: U

注意:FileReader 不支持 mark() / reset()!上述示例中 markSupported() 实际返回 false。若需此功能,应使用 BufferedReader


mark/reset

只有部分 Reader 子类(如 BufferedReaderStringReader)支持标记功能:

import java.io.*;

public class MarkResetExample {
    public static void main(String[] args) throws IOException {
        String text = "Java I/O is powerful!";
        try (Reader reader = new BufferedReader(new StringReader(text))) {
            if (reader.markSupported()) {
                reader.mark(20); // 标记位置

                // 读取前 4 个字符
                for (int i = 0; i < 4; i++) {
                    System.out.print((char) reader.read());
                }
                System.out.println(); // 输出 "Java"

                // 回退到标记位置
                reader.reset();

                // 再次读取,应从头开始
                int ch;
                while ((ch = reader.read()) != -1) {
                    System.out.print((char) ch);
                }
                // 输出完整字符串 "Java I/O is powerful!"
            }
        }
    }
}

构造器

Reader 提供两个受保护的构造器,主要用于子类实现:

protected Reader()
// 同步锁为 this

protected Reader(Object lock)
// 使用指定对象作为同步锁,适用于组合多个流的场景

普通开发者通常不需要直接调用这些构造器。


最佳实践

  1. 优先使用字符流处理文本:避免 FileInputStream + 手动转码导致的乱码

  2. 使用 try-with-resources:确保流在使用后自动关闭

  3. 需要按行读取?用 BufferedReader

    BufferedReader br = new BufferedReader(new FileReader("log.txt"));
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
  4. 指定编码更安全FileReader 使用系统默认编码,跨平台可能出错。推荐:

    Reader reader = new InputStreamReader(
        new FileInputStream("file.txt"), 
        StandardCharsets.UTF_8
    );

重点总结

  • Reader 是所有字符输入流的抽象基类,专为文本读取设计

  • 核心方法包括 read()close()skip()

  • mark() / reset() 并非所有子类都支持(FileReader 不支持)

  • 实际开发中常用 FileReaderBufferedReaderInputStreamReader

  • 务必正确关闭资源,推荐使用 try-with-resources 语法


思考题

  1. 为什么 FileReader 不支持 mark()reset()?哪些 Reader 子类支持?

  2. 如果一个文本文件使用 UTF-8 编码,但在 Windows 系统(默认 GBK)上用 FileReader 读取,可能出现什么问题?如何解决?

  3. read(char[] cbuf)read(char[] cbuf, int off, int len) 在性能和灵活性上有何区别?何时应使用后者?