概况

Reader 是一个用于读字符流的抽象类,它将一些相通的读相关操作抽象到此类,方便各种读操作类的实现。一般来说子类只需要实现它的 read 和 close 两个方法,但如果有需要还可以重写 Reader 提供的公共方法。

JDK 在 Reader 的基础上实现了很多有用的 xxxReader ,包括 BufferedReader、CharArrayReader、FilterReader、InputStreamReader、FileReader、PipedReader、StringReader 和 LineNumberReader 等等。

继承结构

--java.lang.Object
  --java.io.Reader
复制代码

类定义

public abstract class Reader implements Readable, Closeable
复制代码

Reader 被定为 public 且 abstract 的类,实现了 Readable 和 Closeable接口。

Readable 接口表示尝试将字符读取到指定的缓冲中,接口定义如下:

public interface Readable {
    public int read(java.nio.CharBuffer cb) throws IOException;
}
复制代码

Closeable 接口表示 Reader 可以被close,接口定义如下:

public interface Closeable extends AutoCloseable {
    public void close() throws IOException;
}
复制代码

主要属性

private static final int maxSkipBufferSize = 8192;

private char skipBuffer[] = null;

protected Object lock;
复制代码
  • maxSkipBufferSize 最大跳过缓冲的大小。
  • skipBuffer 是一个 char[] 类型,表示跳过缓冲。
  • lock 是 Reader 的锁,用于实现同步。

构造方法

有两种构造方法,不带参数时则将自己作为锁,而如果传入了某个 Object 对象则将其作为锁。

protected Reader() {
    this.lock = this;
}
protected Reader(Object lock) {
    if (lock == null) {
        throw new NullPointerException();
    }
    this.lock = lock;
}
复制代码

主要方法

read方法

一共有四个 read 方法,其中有一个抽象的 read 方法,可以看到所有 read 方法最终都会调用这个抽象方法,提供给子类处理逻辑的实现。它传入的三个参数,字符数组cbuf、偏移量off和数组长度。

public abstract int read(char cbuf[], int off, int len) throws IOException;
复制代码

无参的 read 方法其实是默认读一个字符,new 一个 char 对象然后调用子类实现进行读取,最后返回读到的字符。

public int read() throws IOException {
    char cb[] = new char[1];
    if (read(cb, 0, 1) == -1)
        return -1;
    else
        return cb[0];
}
    
复制代码

假如 read 方法传入的参数为 char 数组时,则直接调用子类实现进行读取。

public int read(char cbuf[]) throws IOException {
    return read(cbuf, 0, cbuf.length);
}
复制代码

最后一个 read 方法其实是 Readable 接口定义的方法,用于读取字符到指定的 CharBuffer 对象中,逻辑是先得到 CharBuffer 对象剩余长度,根据该长度实例化 char 数组,然后调用子类实现完成读取,最后将读取到的字符放进 CharBuffer 对象。

public int read(java.nio.CharBuffer target) throws IOException {
    int len = target.remaining();
    char[] cbuf = new char[len];
    int n = read(cbuf, 0, len);
    if (n > 0)
        target.put(cbuf, 0, n);
    return n;
}
复制代码

ready方法

表示该读取器是否已准备好,默认返回 false,如果能保证调用 read 方法读取下一个字符不阻塞则返回 true。

public boolean ready() throws IOException {
    return false;
}
复制代码

skip方法

该方法用于跳过指定长度字符,期间如果某些字符还未可读则可能发生阻塞,另外期间如果发生 IO 错误则会抛异常。逻辑是,

  1. 跳过字符长度不能小于0。
  2. 跳过长度不能超过最大跳过长度 maxSkipBufferSize,超过则只能取 maxSkipBufferSize。
  3. 加锁开始处理,skipBuffer 对象如果为空则需要先实例化指定长度的 char 数组。
  4. 不断循环调用子类的 read 方法进行读取,对读取到的字符不做处理,即实现了跳过效果。
public long skip(long n) throws IOException {
    if (n < 0L)
        throw new IllegalArgumentException("skip value is negative");
    int nn = (int) Math.min(n, maxSkipBufferSize);
    synchronized (lock) {
        if ((skipBuffer == null) || (skipBuffer.length < nn))
            skipBuffer = new char[nn];
        long r = n;
        while (r > 0) {
            int nc = read(skipBuffer, 0, (int)Math.min(r, nn));
            if (nc == -1)
                break;
            r -= nc;
        }
        return n - r;
    }
}
复制代码

close方法

它是一个抽象方法,留给子类实现。此方法用于关闭流,并且释放相关的资源 。关闭后再调用 read()、ready()、mark()、reset()或skip()等方法将抛出 IO 异常。

public abstract void close() throws IOException;
复制代码

markSupported方法

是否支持 mark 和 reset 操作,这里直接返回 false,子类根据实际重写该方法。

public boolean markSupported() {
    return false;
}
复制代码

mark方法

标记读取器当前的位置,与之对应的是 reset 方法,通过他们之间的组合能实现重复读取操作。默认是不支持此操作的,需要子类重写该方法。

public void mark(int readAheadLimit) throws IOException {
    throw new IOException("mark() not supported");
}
复制代码

reset方法

与 mark 方法对应,它可以重置读取器的位置到上次被 mark 操作标识的位置,如果未被标记过则可能会被重置到开始的位置。默认是不支持此操作的,需要子类重写该方法。

public void reset() throws IOException {
    throw new IOException("reset() not supported");
}
复制代码