【Java源码阅读系列33】深度解读Java FilterReader 源码

FilterReader 是 Java I/O 体系中字符输入流的核心抽象类,位于 java.io 包下。它与 FilterWriter(字符输出流)、FilterOutputStream(字节输出流)共同构成 Java I/O 的「装饰器模式」基础框架,旨在通过动态组合扩展字符输入流的功能。本文将结合源码,从类结构、设计模式、核心方法实现等角度,深入解析 FilterReader 的设计思想。

一、类定义与核心结构

1. 类继承关系与核心成员

public abstract class FilterReader extends Reader {
    protected Reader in; // 被装饰的底层字符输入流

    protected FilterReader(Reader in) {
        super(in); // 调用父类 Reader 的构造方法(传递锁对象)
        this.in = in;
    }
}
  • 继承链FilterReader 继承自抽象类 Reader(所有字符输入流的基类),强制子类实现字符读取的核心能力。
  • 组合设计:通过 protected Reader in 持有一个底层字符输入流实例(如 FileReaderStringReader),这是装饰器模式的核心特征——通过组合而非继承扩展功能。
  • 抽象性:类被声明为 abstract,无法直接实例化,必须由子类(如 BufferedReaderPushbackReader)提供具体实现。

二、设计模式:装饰器模式的字符输入流实践

1. 装饰器模式的核心逻辑

装饰器模式的核心是在不修改原始对象的前提下,动态叠加功能。其关键要素包括:

  • 接口一致性:装饰器与被装饰对象实现相同接口(或继承相同抽象类),保证行为一致;
  • 功能委托:装饰器默认将请求转发给被装饰对象,子类通过重写方法添加额外逻辑;
  • 灵活组合:通过嵌套装饰器,实现多种功能的自由组合(如「缓冲+回退+编码转换」)。

2. FilterReader 如何体现装饰器模式?

  • 接口一致性FilterReader 继承自 Reader,与所有字符输入流(如 FileReader)共享 readskipmark 等核心方法,确保装饰器与原始流可以无缝替换。
  • 基础功能委托:所有方法(如 read()skip(long n))默认直接调用底层流 in 的对应方法(如 in.read()),将基础字符读取功能委托给被装饰对象。
  • 扩展开放性:子类通过重写方法实现功能扩展(例如 BufferedReader 重写 read 方法添加缓冲逻辑,PushbackReader 重写 read 实现字符回退),符合「开闭原则」(对扩展开放,对修改关闭)。

三、核心方法源码解析

FilterReader 的方法设计高度统一,核心逻辑均是将请求转发给底层流 in,以下是关键方法的详细分析:

1. read():读取单个字符

public int read() throws IOException {
    return in.read(); // 直接委托给底层流读取单个字符
}

该方法将单个字符的读取操作直接转发给底层流 in。返回值为读取的字符(int 类型,范围 0-65535),若到达流末尾则返回 -1。子类可重写此方法添加过滤逻辑(例如 CipherReader 可在此解密字符)。

2. read(char[] cbuf, int off, int len):读取字符到数组

public int read(char cbuf[], int off, int len) throws IOException {
    return in.read(cbuf, off, len); // 委托给底层流读取指定区间的字符
}

此方法将字符读取到数组的指定区间,返回实际读取的字符数(若到达流末尾则返回 -1)。默认实现不做任何额外处理,子类(如 BufferedReader)会重写此方法,先从内部缓冲区读取字符,缓冲区不足时再从底层流批量加载数据,减少 I/O 次数。

3. skip(long n):跳过指定数量的字符

public long skip(long n) throws IOException {
    return in.skip(n); // 委托底层流跳过字符
}

跳过 n 个字符后,后续读取操作将从跳过的位置继续。底层流(如 FileReader)的 skip 方法通常通过移动文件指针实现,而装饰器子类(如 BufferedReader)可能通过调整缓冲区指针优化此操作。

4. ready():判断流是否可立即读取

public boolean ready() throws IOException {
    return in.ready(); // 委托底层流判断是否就绪
}

返回 true 表示流已准备好立即读取(例如缓冲区有数据或底层流支持非阻塞读取)。子类可重写此方法(如 BufferedReader 若缓冲区有数据则直接返回 true,无需等待底层流)。

5. mark(int readAheadLimit)reset():标记与重置

public void mark(int readAheadLimit) throws IOException {
    in.mark(readAheadLimit); // 委托底层流标记位置
}

public void reset() throws IOException {
    in.reset(); // 委托底层流重置到标记位置
}

mark 方法在流中标记一个位置,reset 方法将流重置到该位置(仅当流支持标记时有效)。子类(如 BufferedReader)可扩展此功能,例如在缓冲区中保存标记位置,避免直接调用底层流的 mark(可能不支持)。


四、子类扩展示例:以 BufferedReader 为例

BufferedReaderFilterReader 的典型子类(实际 JDK 中 BufferedReader 直接继承 Reader,但设计思想一致),通过添加字符缓冲区优化读取效率:

public class BufferedReader extends Reader {
    private char[] cb; // 字符缓冲区
    private int nChars; // 缓冲区总容量
    private int nextChar; // 缓冲区当前读取位置
    private Reader in; // 底层字符输入流

    public int read(char[] cbuf, int off, int len) throws IOException {
        if (len == 0) return 0;
        // 优先从缓冲区读取
        if (nextChar >= nChars) {
            fill(); // 缓冲区空,从底层流加载数据到缓冲区
            if (nextChar >= nChars) return -1; // 底层流无数据
        }
        // 计算可读取的字符数(缓冲区剩余量与请求量的较小值)
        int avail = nChars - nextChar;
        int cnt = (avail < len) ? avail : len;
        System.arraycopy(cb, nextChar, cbuf, off, cnt);
        nextChar += cnt;
        return cnt;
    }

    private void fill() throws IOException {
        // 从底层流读取数据到缓冲区
        int n = in.read(cb, 0, cb.length);
        if (n > 0) {
            nChars = n;
            nextChar = 0;
        }
    }
}

通过重写 read 方法,BufferedReader 优先从缓冲区读取字符,仅当缓冲区无数据时才调用底层流的 read 方法批量加载数据。这种「预加载-缓冲读取」机制显著减少了 I/O 操作次数,提升了字符输入效率。


五、设计思想总结

  1. 装饰器模式的普适性
    FilterReaderFilterWriterFilterOutputStream 共同构成 Java I/O 的装饰器基础框架,分别处理字符输入、字符输出、字节输出的功能扩展。这种设计使得开发者可以通过嵌套装饰器(如 new BufferedReader(new InputStreamReader(new FileInputStream("a.txt"))))灵活组合「缓冲+编码转换+文件读取」等功能,避免了继承链膨胀。

  2. 抽象类的职责分离
    FilterReader 作为抽象装饰器,仅定义基础行为(转发请求),将具体功能扩展(如缓冲、回退、字符转换)交给子类实现。这种「抽象基类定义框架,子类实现细节」的设计,符合「单一职责原则」。

  3. I/O 流的可组合性
    由于所有过滤字符输入流都基于 FilterReader 或其设计思想,开发者可以根据需求动态叠加功能。例如:

    Reader reader = new FileReader("data.txt");        // 基础文件读取流
    Reader bufferedReader = new BufferedReader(reader); // 缓冲功能(提升读取效率)
    Reader pushbackReader = new PushbackReader(bufferedReader); // 回退功能(允许读取后撤销)
    

六、总结

FilterReader 是 Java 字符输入流体系中装饰器模式的核心实现,通过「继承+组合」的方式,为字符流的功能扩展提供了灵活的基础框架。理解其设计思想,不仅能深入掌握 Java I/O 流的运行机制,更能学会如何通过设计模式解决实际开发中「动态功能扩展」的问题。

你可能感兴趣的:(源码阅读系列之Java,java,开发语言)