14、Reader的源码、FilterReader源码、PushbackReader源码(windows操作系统,JDK8)

一、Reader.class源码

  Reader 是用来读取字符流的装饰器模式中顶层的抽象类,与 InputStream(字节流)不同的是,Reader 专门处理字符char(字符char在JVM中使用Unicode编码占2个byte),主要用于读取和写入中文文本。
  windows操作系统的JDK8版本中,所有的Reader的子类如下(此处只展示部分):
image

  常用的Reader子类有以下5个:
image

①、InputStreamReader:处理文件的字符输入流,在进行文件读操作时,如果遇到不同编码格式,可以使用 InputStreamReader 进行处理。
②、BufferedReader :类似于 BufferedInputStream ,不同点在于BufferedReader 读取的是字符char,BufferedInputStream读取的是字节byte,其内部也带有一个缓冲区(1个char[]数组),一般用BufferedReader来配合其它字符输入流一起使用来减少IO次数,也可以使用mark()函数和 reset()函数重复从缓冲区(1个char[]数组)中读取字符。
③、PushbackReader:以将已读取的字符(char)重新推回输入流中。这样,下次调用read()函数时,这些字符(char)就会再次被读取。因此,PushbackReader可以从输入流中解析字符(char)数据。
④、StringReader:如果输入源是一个字符串的,可以使用 StringReader 来将一个字符串转换为字符流。
以上4个子类的使用方式请参照:
1、Java的IO概览(一)
2、Java的IO概览(二)

  Reader.class的源码如下:

package java.io;

public abstract class Reader implements Readable, Closeable {

    //当这个字符输入流进行read操作、skip操作的时候,用这个对象(锁)来同步线程,来保证多线程场景下操作这个字符输入流时的线程安全性。
    //这个锁对象既可以是Reader.class类型对象本身,也可以是其它类型的对象,但是最有效的同步方式是,在使用synchronized锁定同步代码块的时候,一定要用lock变量指向的锁对象,而不是将synchronized加到函数上面或者用this关键字同步,推荐的用法详情请看skip()函数
    //推荐的同步方式如下:
    /**
     * public long skip(long n) throws IOException {
     *   ...省略部分代码...
     *  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;
     *  }
     *}
     *    
     */
     //以下2种方式不推荐
    //不推荐的同步方式①:将synchronized关键字加到函数上面
    /**
     * public synchronized long skip(long n) throws IOException {
     *   ...省略部分代码...
     * 
     */
    //不推荐的同步方式②:将lock改成this关键字
    /**
     * public long skip(long n) throws IOException {
     *   ...省略部分代码...
     *  synchronized (this) {
     *      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;
     *  }
     *}
     *    
     */
    protected Object lock;

    //构造函数,并将用于多线程场景下线程同步的lock变量指向this(当前这个字符输入流对象)本身
    protected Reader() {
        this.lock = this;
    }

    //构造函数,并将用于多线程场景下线程同步的lock变量指向其它对象而不是this(当前这个字符输入流对象)本身
    protected Reader(Object lock) {
        if (lock == null) {
            throw new NullPointerException();
        }
        this.lock = lock;
    }

    //将从字符输入流中读取n个字符放入到NIO中的CharBuffer对象(字符缓冲区对象)里面
    //当CharBuffer对象(字符缓冲区对象)中可用的空间>0时,这个函数是阻塞的,直到读取到数据或者抛出异常。
    public int read(java.nio.CharBuffer target) throws IOException {
        int len = target.remaining();//计算NIO中的CharBuffer对象(字符缓冲区对象)中还可以放多少个字符(可用的空间)
        char[] cbuf = new char[len];//构建一个长度为len的字符数组char[],len表示CharBuffer对象(字符缓冲区对象)中还可以放的字符数量
        int n = read(cbuf, 0, len);//从字符输入流中读取len个字符放到字符数组char[] cbuf的[0,len)索引位置,并返回从字符输入流中读取到的字符数量
        if (n > 0)
            //如果从字符输入流中读取到的字符数量>0,则将这些字符(假设有n个字符,0<n<=len)都放入到CharBuffer对象(字符缓冲区对象)中
            target.put(cbuf, 0, n);
        return n;//返回n,这里的n取值范围为0<=n<=len,或者[0,len]
    }

     //将从字符输入流中读取1个字符
     //和InputStream一样,在读取到数据或者抛出异常前,这个函数是阻塞的。
    public int read() throws IOException {
        char cb[] = new char[1];//构建一个长度为1的字符数组char[] cb
        //将从字符输入流中读取1个字符放入到字符数组char[] cb的[0,1)索引位置,如果字符输入流已经读完了(即已经读取到了文件的EOF符号),则会返回-1,read(cb, 0, 1)函数是留给子类实现的
        if (read(cb, 0, 1) == -1)
            return -1;//如果字符输入流已经读完了(即已经读取到了文件的EOF符号),则返回-1
        else
            return cb[0];//否则,返回读取到的字符
    }

    //将从字符输入流中读取length个字符到字符数组char[] cbuf中,length表示字符数组char[] cbuf的长度
    //和InputStream一样,在读取到数据或者抛出异常前,这个函数是阻塞的。
    public int read(char cbuf[]) throws IOException {
        return read(cbuf, 0, cbuf.length);
    }
    
    //留给子类实现,如果读到了字符输入流的末尾(即已经读取到了文件的EOF符号)则会返回-1
    abstract public int read(char cbuf[], int off, int len) throws IOException;

    //每一次从字符输入流中最多跳过的字符数量
    private static final int maxSkipBufferSize = 8192;

    //执行skip()函数时才会用到,用来跳过指定的字符数量时需要借助字符数组char[]来完成
    private char skipBuffer[] = null;

    //将从字符输入流中跳过n个字符
    public long skip(long n) throws IOException {
        if (n < 0L)
            //校验,如果n<0,则抛出一个IllegalArgumentException
            throw new IllegalArgumentException("skip value is negative");
        //每次跳过的字符最多为8192个
        int nn = (int) Math.min(n, maxSkipBufferSize);
        synchronized (lock) {//后续的代码片段只允许单线程执行
            if ((skipBuffer == null) || (skipBuffer.length < nn))
                skipBuffer = new char[nn];//用于每次跳过指定数量字符(每次最多为8192个)的字符数组
            long r = n;//r表示当前线程还没(或者还需要)跳过的字符的总数量
            while (r > 0) {
                //当前线程还没(或者还需要)跳过的字符的总数量>0时,每次执行read()函数从字符输入流中跳过0~8192个字符
                //当前线程读到了字符输入流的末尾(即已经读取到了文件的EOF符号)则会返回-1
                int nc = read(skipBuffer, 0, (int)Math.min(r, nn));
                if (nc == -1)
                    break;//中断while循环
                r -= nc;//将本次从字符输入流中跳过的字符递减就是接下来还没(或者还需要)跳过的字符的总数量
            }
            return n - r;//返回已经从字符输入流中跳过的字符总数量(n-r)
        }
    }

    //具体逻辑需要子类来重写,JDK给这个函数的定义如下:
    //①、如果返回 true,则表示下一次调用read()函数时保证不会阻塞,否则返回 false。
    //②、即使返回 false 也不能保证下一次调用read()函数时一定会阻塞。
    public boolean ready() throws IOException {
        return false;
    }

     //具体逻辑需要子类来重写,JDK给这个函数的定义如下:
     //①、如果返回true,表示子类支持mark()函数;
     //②、如果返回false,表示子类不支持mark()函数
    public boolean markSupported() {
        return false;
    }
    
    //标记此字符输入流中的当前位置。随后调用reset()函数时会将此字符输入流重新定位到上次标记的位置,从而使得后续的读取操作能够再次读取相同的字符。
    //ASCIIReader、BufferedReader、CharArrayReader、FilterReader、Latin1Reader、LineNumberReader、StringReader、UCSReader会重写这个mark()函数,其余Reader.class的子类不会重写这个函数。
    public void mark(int readAheadLimit) throws IOException {
        throw new IOException("mark() not supported");
    }

    //reset()函数会将此字符输入流重新定位到上次执行mark()函数标记的位置,从而使得后续的读取操作能够再次读取相同的字符。
    //ASCIIReader、BufferedReader、CharArrayReader、FilterReader、Latin1Reader、LineNumberReader、StringReader、UCSReader会重写这个reset()函数,其余Reader.class的子类不会重写这个函数。
    public void reset() throws IOException {
        throw new IOException("reset() not supported");
    }

    /**
     * Closes the stream and releases any system resources associated with
     * it.  Once the stream has been closed, further read(), ready(),
     * mark(), reset(), or skip() invocations will throw an IOException.
     * Closing a previously closed stream has no effect.
     *
     * @exception  IOException  If an I/O error occurs
     */
     //留给子类实现,子类必须遵守以下规则:
     //①、关闭这个字符输入流,并释放与该流相关的系统资源
     //②、关闭的字符输入流对象在执行read()函数, ready()函数, mark()函数, reset()函数, 或者 skip()函数 时将抛出一个 IOException.
     abstract public void close() throws IOException;

}
1.1、Reader的skip()函数
    //将从字符输入流中跳过n个字符
    public long skip(long n) throws IOException {
        if (n < 0L)
            //校验,如果n<0,则抛出一个IllegalArgumentException
            throw new IllegalArgumentException("skip value is negative");
        //每次跳过的字符最多为8192个
        int nn = (int) Math.min(n, maxSkipBufferSize);
        synchronized (lock) {//后续的代码片段只允许单线程执行
            if ((skipBuffer == null) || (skipBuffer.length < nn))
                skipBuffer = new char[nn];//用于每次跳过指定数量字符(每次最多为8192个)的字符数组
            long r = n;//r表示当前线程还没(或者还需要)跳过的字符的总数量
            while (r > 0) {
                //当前线程还没(或者还需要)跳过的字符的总数量>0时,每次执行read()函数从字符输入流中跳过0~8192个字符
                //当前线程读到了字符输入流的末尾(即已经读取到了文件的EOF符号)则会返回-1
                int nc = read(skipBuffer, 0, (int)Math.min(r, nn));
                if (nc == -1)
                    break;//中断while循环
                r -= nc;//将本次从字符输入流中跳过的字符递减就是接下来还没(或者还需要)跳过的字符的总数量
            }
            return n - r;//返回已经从字符输入流中跳过的字符总数量(n-r)
        }
    }

如果一个字符输入流中有20000个字符,并且此时有多个线程(此处假设有2个线程,分别为ThreadA和ThreadB)要执行此字符输入流的skip()函数,ThreadA执行了skip(8000)表示要从这个字符输入流中跳过8000个字符,ThreadB执行了skip(10000)表示要从这个字符输入流中跳过10000个字符,此时,这2个线程同时执行skip()函数的过程如下所示:
①、ThreadA和ThreadB分别在各自的线程栈中压入skip()函数,如下所示:
image

②、假如ThreadA在执行下面的代码时先获取到了锁,先进入到了Monitor监控(synchronized底层是基于操作系统的Monitor来实现的)的代码片段

        synchronized (lock) {//后续的代码片段只允许单线程执行

那么ThreadA在进入while循环之前,会进行初始化char[]数组的长度为8000和临时变量long r的操作,如下所示:
image

③、ThreadA只会进入一次while循环,ThreadA在进入while循环之后,会从字符输入流中读取8000个字符到字符数组char[] skipBuffer中,然后就结束while循环,并且释放掉获取到的锁对象Object lock,并且将从字符输入流中跳过的字符总数量(这里是8000)返回给该函数的调用者,如下所示:
image
image

④、然后ThreadB在执行下面的代码时也获取到了锁,进入到了Monitor监控(synchronized底层是基于操作系统的Monitor来实现的)的代码片段

    synchronized (lock) {//后续的代码片段只允许单线程执行

那么ThreadB在进入while循环之前,会重新初始化char[]数组的长度为8192和临时变量long r的操作,如下所示:
image

⑤、ThreadB第1次进入while循环之后,会从字符输入流中读取8192个字符到字符数组char[] skipBuffer中,然后更新long r = 1808,如下所示:
image

⑥、ThreadB第2次进入while循环之后,会从字符输入流中读取1808个字符到字符数组char[] skipBuffer中,然后更新long r = 0,如下所示:
image

⑦、最后,ThreadB结束while循环,并且释放掉获取到的锁对象Object lock,并且将从字符输入流中跳过的字符总数量(这里是10000)返回给该函数的调用者,如下所示:
image

二、FilterReader.class源码——字符流的装饰器基类

  FilterReader 的UML关系图,如下所示:
image

  FilterReader.class的源码,如下所示:

package java.io;


/**
 * Abstract class for reading filtered character streams.
 * The abstract class <code>FilterReader</code> itself
 * provides default methods that pass all requests to
 * the contained stream. Subclasses of <code>FilterReader</code>
 * should override some of these methods and may also provide
 * additional methods and fields.
 *
 * @author      Mark Reinhold
 * @since       JDK1.1
 */

public abstract class FilterReader extends Reader {

    //实际被装饰的字符输入流
    protected Reader in;
    
    //当用构造函数创建这个装饰器时,传入一个被装饰者的字符输入流
    protected FilterReader(Reader in) {
        super(in);
        this.in = in;
    }

    //调用实际被装饰的字符输入流的read() 函数
    public int read() throws IOException {
        return in.read();
    }

    //调用实际被装饰的字符输入流的read(char cbuf[], int off, int len) 函数
    public int read(char cbuf[], int off, int len) throws IOException {
        return in.read(cbuf, off, len);
    }

    //调用实际被装饰的字符输入流的skip(long n) 函数
    public long skip(long n) throws IOException {
        return in.skip(n);
    }

    //调用实际被装饰的字符输入流的ready() 函数
    public boolean ready() throws IOException {
        return in.ready();
    }

    //调用实际被装饰的字符输入流的markSupported() 函数
    public boolean markSupported() {
        return in.markSupported();
    }

    //调用实际被装饰的字符输入流的mark(int readAheadLimit) 函数
    public void mark(int readAheadLimit) throws IOException {
        in.mark(readAheadLimit);
    }

    //调用实际被装饰的字符输入流的reset() 函数
    public void reset() throws IOException {
        in.reset();
    }
    
    //调用实际被装饰的字符输入流的close() 函数
    public void close() throws IOException {
        in.close();
    }

}

三、PushbackReader.class源码——可以回退字符到被读取的字符流中的字符流装饰器类

  PushbackReader与PushbackInputStream类似,只不过PushbackReader提供了有限字符(内部定义了一个默认长度为1的char[] buf字符数组)的缓冲式回退能力,具体过程如下:
①、当调用unread()函数时会将任意字符(可以是从被装饰的输入流中读取的字节,也可以是自己定义的字符)压入char[] buf字符数组的尾部(pos-1的位置);
②、当后续调用read()函数时优先读取步骤①中这个char[] buf字符数组中被压入的字符。
  PushbackInputStream.class的相关源码和使用方式请参照:13、PushbackInputStream和StreamTokenizer的源码分析和使用方法详细分析

3.1、PushbackReader.class的源码分析

  PushbackReader.class 的UML关系图,如下所示:
image

  PushbackReader.class 的源码,如下所示:

package java.io;


public class PushbackReader extends FilterReader {

    //有限长度的用于回退的字符数组缓冲区,默认长度为1
    private char[] buf;

    //可读指针,char[] buf(有限长度的用于回退的字符数组缓冲区)中该指针(包括该指针)索引之后的所有字符都可以读
    private int pos;

    //构造函数,in为被装饰的字符输入流,size为char[] buf(有限长度的用于回退的字符数组缓冲区)的长度
    public PushbackReader(Reader in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("size <= 0");
        }
        this.buf = new char[size];
        this.pos = size;//将可读指针指向char[] buf(有限长度的用于回退的字符数组缓冲区)中最后一个索引(size-1)之后
    }

    //构造函数,in为被装饰的字符输入流
    public PushbackReader(Reader in) {
        this(in, 1);//构造一个默认长度为1的char[] buf(用于回退的字符数组缓冲区)
    }

    //检查被装饰的字符输入流是否关闭
    private void ensureOpen() throws IOException {
        if (buf == null)
            throw new IOException("Stream closed");
    }
    
    //这个函数在多线程的场景下运行时是线程安全的
    //如果char[] buf(用于回退的字符数组缓冲区)中有可读的字符的话,就从该缓冲区中读取1个字符
    //如果char[] buf(用于回退的字符数组缓冲区)中没有可读的字符的话,就从被装饰的输入流中读取1个字符
    //如果char[] buf(用于回退的字符数组缓冲区)和被装饰的输入流中都没有可读的字符的话,返回-1
    public int read() throws IOException {
        synchronized (lock) {//用Reader.class::lock变量指向的对象(锁)来同步线程
            ensureOpen();
            if (pos < buf.length)
                return buf[pos++];
            else
                return super.read();
        }
    }

    //这个函数在多线程的场景下运行时是线程安全的,尽可能的从char[] buf(用于回退的字符数组缓冲区)和被装饰的输入流中读取len个字符到char[] cbuf的[off,off+len)索引位置,总共分为以下5种场景:
    //①、如果char[] buf(用于回退的字符数组缓冲区)中有len个字符的话,就从该缓冲区中读取len个字符到字符数组char[] cbuf的[off,off+len)索引位置
    //②、如果char[] buf(用于回退的字符数组缓冲区)中没有任何字符并且被装饰的字符输入流中有len个字符,那就从被装饰的字符输入流中读取len个字符到字符数组char[] cbuf的[off,off+len)索引位置
    //③、如果char[] buf(用于回退的字符数组缓冲区)中没有任何字符并且被装饰的字符输入流中只有avail(avail<len)个字符,那就从被装饰的字符输入流中读取avail个字符到字符数组char[] cbuf的[off,off+avail)索引位置
    //④、如果char[] buf(用于回退的字符数组缓冲区)中有avail(avail<len)个字符的话,就读取avail个字符,剩余len-avail个字符从被装饰的字符输入流中读取,如果被装饰的字符输入流中没有len-avail个字符的话,那就从被装饰的输入流中有多少读取多少,直到将被装饰的输入流读取完毕,然后将以上2个地方(被装饰的输入流+用于回退的字符数组缓冲区)读取的所有字符(假如有x个)放入到字符数组char[] cbuf的[off,off+x)索引位置
    //⑤、如果char[] buf(用于回退的字符数组缓冲区)和被装饰的字符输入流中都没有任何字符的话,返回-1
    public int read(char cbuf[], int off, int len) throws IOException {
        synchronized (lock) {
            //检查被装饰的字符输入流是否关闭
            ensureOpen();
            try {
                if (len <= 0) {
                    if (len < 0) {
                        throw new IndexOutOfBoundsException();
                    } else if ((off < 0) || (off > cbuf.length)) {//相当于off + len > cbuf.length(源码中这样写代码的好处我没看出来)
                        throw new IndexOutOfBoundsException();
                    }
                    return 0;//要从PushbackReader 对象中读取的len个字符==0时,返回0
                }
                int avail = buf.length - pos;//用于回退的字符数组缓冲区中实际装载了buf.length - pos个字符
                if (avail > 0) {
                    if (len < avail)
                        avail = len;
                    System.arraycopy(buf, pos, cbuf, off, avail);
                    pos += avail;
                    off += avail;
                    len -= avail;
                }
                if (len > 0) {
                    len = super.read(cbuf, off, len);
                    if (len == -1) {
                        return (avail == 0) ? -1 : avail;
                    }
                    return avail + len;//场景④中的x就是这里的avail + len
                }
                return avail;
            } catch (ArrayIndexOutOfBoundsException e) {
                throw new IndexOutOfBoundsException();
            }
        }
    }

    //一次只可以回推1个字符数据到char[] buf(用于回退的字符数组缓冲区)中
    public void unread(int c) throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (pos == 0)//pos=0时,表示char[] buf(用于回退的字符数组缓冲区)中已经没有足够的容量再放置数据,所以抛出一个IOException异常。
                throw new IOException("Pushback buffer overflow");
            buf[--pos] = (char) c;
        }
    }

    //一次回推char[] cbuf字符数组中[off,off+len)索引位置的len个字符数据到char[] buf(用于回退的字符数组缓冲区)中
    public void unread(char cbuf[], int off, int len) throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (len > pos)//如果char[] buf(用于回退的字符数组缓冲区)中没有足够的位置放置len个字符,则抛出一个IOException
                throw new IOException("Pushback buffer overflow");
            pos -= len;//如果char[] buf(用于回退的字符数组缓冲区)中有足够的位置放置len个字符,则使用System.arraycopy()函数进行回退
            System.arraycopy(cbuf, off, buf, pos, len);
        }
    }


    public void unread(char cbuf[]) throws IOException {
        unread(cbuf, 0, cbuf.length);
    }

    
    public boolean ready() throws IOException {
        synchronized (lock) {
            ensureOpen();
            return (pos < buf.length) || super.ready();
        }
    }

    //不支持mark()函数
    public void mark(int readAheadLimit) throws IOException {
        throw new IOException("mark/reset not supported");
    }

    //不支持reset()函数
    public void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }

    //不支持mark()函数
    public boolean markSupported() {
        return false;
    }

    //关闭被装饰的字符输入流和用于回退的字符数组缓冲区
    public void close() throws IOException {
        super.close();
        buf = null;
    }

    //从char[] buf(用于回退的字符数组缓冲区)+被装饰的字符输入流中跳过n个字符,如果char[] buf(用于回退的字符数组缓冲区)+被装饰的字符输入流中的字符数量<n,则返回实际跳过的字符数量
    public long skip(long n) throws IOException {
        if (n < 0L)
            throw new IllegalArgumentException("skip value is negative");
        synchronized (lock) {
            ensureOpen();
            int avail = buf.length - pos;//从char[] buf(用于回退的字符数组缓冲区)中跳过的字符
            if (avail > 0) {
                if (n <= avail) {
                    pos += n;
                    return n;
                } else {
                    pos = buf.length;
                    n -= avail;
                }
            }
            return avail + super.skip(n);//从被装饰的字符输入流中跳过的字符累加到从char[] buf(用于回退的字符数组缓冲区)中跳过的字符
        }
    }
}
3.2、PushbackReader.class的read()函数和unread()函数
package java.io;
public class PushbackReader extends FilterReader {
    //有限长度的用于回退的字符数组缓冲区,默认长度为1
    private char[] buf;
    //可读指针,char[] buf(有限长度的用于回退的字符数组缓冲区)中该指针(包括该指针)索引之后的所有字符都可以读
    private int pos;
    
    ...省略部分代码...
    //这个函数在多线程的场景下运行时是线程安全的,尽可能的从char[] buf(用于回退的字符数组缓冲区)和被装饰的输入流中读取len个字符到char[] cbuf的[off,off+len)索引位置,总共分为以下5种场景:
    //①、如果char[] buf(用于回退的字符数组缓冲区)中有len个字符的话,就从该缓冲区中读取len个字符到字符数组char[] cbuf的[off,off+len)索引位置
    //②、如果char[] buf(用于回退的字符数组缓冲区)中没有任何字符并且被装饰的字符输入流中有len个字符,那就从被装饰的字符输入流中读取len个字符到字符数组char[] cbuf的[off,off+len)索引位置
    //③、如果char[] buf(用于回退的字符数组缓冲区)中没有任何字符并且被装饰的字符输入流中只有avail(avail<len)个字符,那就从被装饰的字符输入流中读取avail个字符到字符数组char[] cbuf的[off,off+avail)索引位置
    //④、如果char[] buf(用于回退的字符数组缓冲区)中有avail(avail<len)个字符的话,就读取avail个字符,剩余len-avail个字符从被装饰的字符输入流中读取,如果被装饰的字符输入流中没有len-avail个字符的话,那就从被装饰的输入流中有多少读取多少,直到将被装饰的输入流读取完毕,然后将以上2个地方(被装饰的输入流+用于回退的字符数组缓冲区)读取的所有字符(假如有x个)放入到字符数组char[] cbuf的[off,off+x)索引位置
    //⑤、如果char[] buf(用于回退的字符数组缓冲区)和被装饰的字符输入流中都没有任何字符的话,返回-1
    public int read(char cbuf[], int off, int len) throws IOException {
        synchronized (lock) {
            //检查被装饰的字符输入流是否关闭
            ensureOpen();
            try {
                if (len <= 0) {
                    if (len < 0) {
                        throw new IndexOutOfBoundsException();
                    } else if ((off < 0) || (off > cbuf.length)) {//相当于off + len > cbuf.length(源码中这样写代码的好处我没看出来)
                        throw new IndexOutOfBoundsException();
                    }
                    return 0;//要从PushbackReader 对象中读取的len个字符==0时,返回0
                }
                int avail = buf.length - pos;//用于回退的字符数组缓冲区中实际装载了buf.length - pos个字符
                if (avail > 0) {
                    if (len < avail)
                        avail = len;
                    System.arraycopy(buf, pos, cbuf, off, avail);
                    pos += avail;
                    off += avail;
                    len -= avail;
                }
                if (len > 0) {
                    len = super.read(cbuf, off, len);
                    if (len == -1) {
                        return (avail == 0) ? -1 : avail;
                    }
                    return avail + len;//场景④中的x就是这里的avail + len
                }
                return avail;
            } catch (ArrayIndexOutOfBoundsException e) {
                throw new IndexOutOfBoundsException();
            }
        }
    }

    //一次只可以回推1个字符数据到char[] buf(用于回退的字符数组缓冲区)中
    public void unread(int c) throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (pos == 0)//pos=0时,表示char[] buf(用于回退的字符数组缓冲区)中已经没有足够的容量再放置数据,所以抛出一个IOException异常。
                throw new IOException("Pushback buffer overflow");
            buf[--pos] = (char) c;
        }
    }

    ...省略部分代码...
}

  如果使用者使用的被装饰的字符输入流是CharArrayReader,然后执行PushbackReader.class的read()函数和unread()函数时,使用如下代码:

package com.xxx.bio;

import java.io.CharArrayReader;
import java.io.IOException;
import java.io.PushbackReader;
import java.io.Reader;
public class PushbackReaderTest {
    public static void main(String[] args) throws IOException {
        String str = "你好,世界,我将征服你";
        Reader reader = new CharArrayReader(str.toCharArray());
        //构建字符回退流
        PushbackReader pushbackReader = new PushbackReader(reader, 8);
        int len = -1;
        System.out.println("输出内容:");
        while ((len = pushbackReader.read()) != -1) {
            //转为char类型
            char c = (char) len;
            if (c == ',') {//此处为中文","
                //为,号时 ,先往前读3个,再往后倒两个
                char[] b1 = new char[3];
                pushbackReader.read(b1);
                //往后倒两个
                pushbackReader.unread(b1, 0, 2);
            } else {
                System.out.print(c);
            }
        }
    }
}

上面代码的执行结果如下:
image

以上代码的整个执行过程分为以下5步:
①、通过构造函数构建一个长度为8的char[] buf(用于回退的字符数组缓冲区)和CharArrayReader.class类型的被装饰的字符输入流,如下所示:

PushbackReader pushbackReader = new PushbackReader(reader, 8);

image

②、按照顺序从CharArrayReader.class类型的输入流中读取字符,直到读取到中文“,”时,如下所示:

        while ((len = pushbackReader.read()) != -1) {
            //转为char类型
            char c = (char) len;
            if (c == ',') {//此处为中文","

            } else {
                System.out.print(c);
            }
        }

image

输出如下:

你好

③、当第一次读取到中文“,”之后,从被装饰的字符输入流CharArrayReader.class中往char[] b1字符数组中读取3个字符,如下所示:

                //为,号时 ,先往前读3个,再往后倒两个
                char[] b1 = new char[3];
                pushbackReader.read(b1);

④、将步骤③中读入到char[] b1字符数组中的[0,2)索引位置的数据读取到PushbackReader中的char[] buf(用于回退的字符数组缓冲区)的[6,8)索引位置中,如下所示:

                //往后倒两个
                pushbackReader.unread(b1, 0, 2);

image

⑤、再次重复执行步骤②中按照顺序从CharArrayReader.class类型的字符输入流中读取字符时,先读取PushbackReader中的char[] buf(用于回退的字符数组缓冲区)的[6,8)索引位置,再从CharArrayReader.class类型的字符输入流中读取剩余字符,如下所示:

        while ((len = pushbackReader.read()) != -1) {
            //转为char类型
            char c = (char) len;
            if (c == ',') {//此处为中文","

            } else {
                System.out.print(c);
            }
        }

image

输出如下:

世界我将征服你
posted @ 2026-06-29 11:45  Carey_ccl  阅读(114)  评论(0)    收藏  举报