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[] skipBu

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐