Java I/O Reader及其子类源码解析

本文深入剖析Java中Reader和Writer类的继承体系结构及其实现细节,包括BufferedReader、CharArrayReader、FilterReader等关键子类,以及它们如何提高读写效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

设计Reader和Writer继承层次结构的主要目的是为了国际化,老的I/O流继承层次结构仅仅支持8位字节流并且不能很好的处理16位的Unicode字符,但是新类的采用的设计比旧类库更快,因此我们应该尽可能了的使用Reader 与Writer 。

我们首先还是先看看他的继承体系


我们首先分析父类 Reader的定义:

/**
 * 首先Reader是所有基于字符流操作的基类,其实现 Readable,以及Closeable接口
 * 其中 Readable接口中的read()方法实现了将字符串读入charBuffer中,但是只有在需要输出的时候才会调用。
 *
 * 用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。
 * 但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。
 */
public abstract class Reader implements Readable, Closeable {

    protected Object lock;

    protected Reader() {        //受保护的构造方法
        this.lock = this;       //用于初始化锁
    }

    protected Reader(Object lock) {     //使用锁的构造方法
        if (lock == null) {
            throw new NullPointerException();
        }
        this.lock = lock;               //初始化锁
    }

    // 将输入流管道中的target.length个字节读取到指定的缓存字符数组target中、返回实际存放到target中的字符数,实际为Readable定义的方法
    public int read(java.nio.CharBuffer target) throws IOException {
        int len = target.remaining();   //  target.remaining() 返回当前流位置与流最大值之间的差值(实际就是剩余的字符数)
        char[] cbuf = new char[len];    //声明一个字符数组
        int n = read(cbuf, 0, len);     //从流中读取 0-len个字符到cbuf中
        if (n > 0)                      //如果写入的字符数大于0
            target.put(cbuf, 0, n);     //将给定字符写入此缓冲区的当前位置,然后该位置递增。
        return n;                       //返回写入的实际数量
    }

    public int read() throws IOException {  //读取一个字符的数据
        char cb[] = new char[1];            //声明一个字符数组
        if (read(cb, 0, 1) == -1)           //读取一个字符到cb中
            return -1;
        else
            return cb[0];                   //返回读取的数据
    }
    
    abstract public int read(char cbuf[], int off, int len) throws IOException;

    private static final int maxSkipBufferSize = 8192;      //最大的字符丢弃数

    private char skipBuffer[] = null;                       //丢弃字符缓冲器

    public long skip(long n) throws IOException {
        //丢弃字符实现
    }
}

该类的源码似乎没有什么需要注意的地方,相对于InputStream而已,仅仅是读取的是字符而已。

再来针对其子类一个一个的看,首先是

BufferedReader:

作用:这个类就是一个包装类,它可以包装字符流,将字符流放入缓存里面,等待缓存满了或者调用flush的时候,在读入到内存,就是为了提供读的效率而设计的。

先来看看其定义:

/**
 * BufferReader的作用是为其它Reader提供缓冲功能。创建BufferReader时,我们会通过它的构造函数指定某个Reader为参数。
 * BufferReader会将该Reader中的数据分批读取,每次读取一部分到缓冲中;操作完缓冲中的这部分数据之后,再从Reader中读取下一部分的数据。
 * 为什么需要缓冲呢?原因很简单,效率问题!缓冲中的数据实际上是保存在内存中,而原始数据可能是保存在硬盘或NandFlash中;
 * 而我们知道,从内存中读取数据的速度比从硬盘读取数据的速度至少快10倍以上。那干嘛不干脆一次性将全部数据都读取到缓冲中呢?
 * 第一,读取全部的数据所需要的时间可能会很长。第二,内存价格很贵,容量不想硬盘那么大。
 */
public class BufferedReader extends Reader {

    private Reader in;      //包装维护类 Reader

    private char cb[];      //字符缓冲区
    private int nChars, nextChar;   //nChars 是cb缓冲区中字符的总的个数 ,nextChar 是下一个要读取的字符在cb缓冲区中的位置

    private static final int INVALIDATED = -2;  //设置了标记,但是被标记位置太长,导致标记无效
    private static final int UNMARKED = -1;     //没有设置过标记。

    private int markedChar = UNMARKED;          //标记值,默认没有设置标记
    private int readAheadLimit = 0; /* Valid only when markedChar > 0 */  //标记能标记位置的最大长度

    /** If the next character is a line feed, skip it */
    private boolean skipLF = false; //skipLF(即skip Line Feed)是“是否忽略换行符”标记

    /** The skipLF flag when the mark was set */
    private boolean markedSkipLF = false;   //设置“标记”时,保存的skipLF的值

    private static int defaultCharBufferSize = 8192;    //默认字符缓冲区大小
    private static int defaultExpectedLineLength = 80;  //默认每一行的字符个数


    public BufferedReader(Reader in, int sz) {  //创建“Reader”对应的BufferedReader对象,sz是BufferedReader的缓冲区大小
        this.in = in;
        cb = new char[sz];
        nextChar = nChars = 0;
    }

    //使用默认的缓冲区大小8192 创建BufferedReader对象
    public BufferedReader(Reader in) {
        this(in, defaultCharBufferSize);
    }

    //确保“BufferedReader”是打开状态
    private void ensureOpen() throws IOException {
        if (in == null)
            throw new IOException("Stream closed");
    }

    /**
     *  填充缓冲区函数。有以下两种情况被调用:
     *  缓冲区没有数据时,通过fill()可以向缓冲区填充数据。
     *  缓冲区数据被读完,需更新时,通过fill()可以更新缓冲区的数据。
     */
    private void fill() throws IOException {
        int dst;        // 表示“cb中填充数据的起始位置”。
        if (markedChar <= UNMARKED) {
            /* No mark */
            dst = 0;    //没有标记的情况,则设dst=0。
        } else {
            /* Marked */
            int delta = nextChar - markedChar;  //delta表示“当前标记的长度”,它等于“下一个被读取字符的位置”减去“标记的位置”的差值;
            if (delta >= readAheadLimit) {      //若“当前标记的长度”超过了“标记上限(readAheadLimit)
                /* Gone past read-ahead limit: Invalidate mark */
                markedChar = INVALIDATED;
                readAheadLimit = 0;             //丢弃标记
                dst = 0;
            } else {
                if (readAheadLimit <= cb.length) {
                    /* Shuffle in the current buffer */
                    // 若“当前标记的长度”没有超过了“标记上限(readAheadLimit)”,
                    // 并且“标记上限(readAheadLimit)”小于/等于“缓冲的长度”;
                    // 则先将“下一个要被读取的位置,距离我们标记的置符的距离”间的字符保存到cb中。
                    System.arraycopy(cb, markedChar, cb, 0, delta);
                    markedChar = 0;
                    dst = delta;
                } else {
                    // 若“当前标记的长度”没有超过了“标记上限(readAheadLimit)”,
                    // 并且“标记上限(readAheadLimit)”大于“缓冲的长度”;
                    // 则重新设置缓冲区大小,并将“下一个要被读取的位置,距离我们标记的置符的距离”间的字符保存到cb中。
                    /* Reallocate buffer to accommodate read-ahead limit */
                    char ncb[] = new char[readAheadLimit];
                    System.arraycopy(cb, markedChar, ncb, 0, delta);
                    cb = ncb;
                    markedChar = 0;
                    dst = delta;
                }
                //更新nextChar和nChars
                nextChar = nChars = delta;
            }
        }

        int n;
        do {
            // 从“in”中读取数据,并存储到字符数组cb中;
            // 从cb的dst位置开始存储,读取的字符个数是cb.length - dst
            // n是实际读取的字符个数;若n==0(即一个也没读到),则继续读取!
            n = in.read(cb, dst, cb.length - dst);
        } while (n == 0);
        // 如果从“in”中读到了数据,则设置nChars(cb中字符的数目)=dst+n,
        // 并且nextChar(下一个被读取的字符的位置)=dst。
        if (n > 0) {
            nChars = dst + n;
            nextChar = dst;
        }
    }

    //从BufferedReader中读取一个字符,该字符以int的方式返回
    public int read() throws IOException {
        synchronized (lock) {
            ensureOpen();
            for (;;) {
                // 若“缓冲区的数据已经被读完”,
                // 则先通过fill()更新缓冲区数据
                if (nextChar >= nChars) {
                    fill();
                    if (nextChar >= nChars)
                        return -1;
                }
                // 若要“忽略换行符”,
                // 则对下一个字符是否是换行符进行处理。
                if (skipLF) {
                    skipLF = false;
                    if (cb[nextChar] == '\n') {
                        nextChar++;
                        continue;
                    }
                }
                // 返回下一个字符
                return cb[nextChar++];
            }
        }
    }

    // 将缓冲区中的数据写入到数组cbuf中。off是数组cbuf中的写入起始位置,len是写入长度
    private int read1(char[] cbuf, int off, int len) throws IOException {
        if (nextChar >= nChars) {   // 若“缓冲区的数据已经被读完”,则更新缓冲区数据。
            if (len >= cb.length && markedChar <= UNMARKED && !skipLF) {
                return in.read(cbuf, off, len);
            }
            fill();
        }
        if (nextChar >= nChars) return -1;  // 若更新数据之后,没有任何变化;则退出。
        if (skipLF) {       // 若要“忽略换行符”,则进行相应处理
            skipLF = false;
            if (cb[nextChar] == '\n') {
                nextChar++;
                if (nextChar >= nChars)
                    fill();
                if (nextChar >= nChars)
                    return -1;
            }
        }
        int n = Math.min(len, nChars - nextChar);
        System.arraycopy(cb, nextChar, cbuf, off, n);   // 拷贝字符操作
        nextChar += n;
        return n;           //返回实际写入的字符数
    }

    //对read1()的封装,添加了“同步处理”和“阻塞式读取”等功能
    public int read(char cbuf[], int off, int len) throws IOException {
        synchronized (lock) {       //  1.方法是同步的

            //2.使用的是贪婪读,除非读到的字符数符合要求或者已经读完,否则一直读
            while ((n < len) && in.ready()) {
                int n1 = read1(cbuf, off + n, len - n);
            }
            return n;   //返回实际的读取数量
        }
    }

    //读取一行数据。ignoreLF是“是否忽略换行符”
    String readLine(boolean ignoreLF) throws IOException {
        StringBuffer s = null;
        int startChar;

        synchronized (lock) {
            ensureOpen();
            boolean omitLF = ignoreLF || skipLF;

            bufferLoop:
            for (;;) {

                if (nextChar >= nChars)
                    fill();
                if (nextChar >= nChars) { /* EOF */
                    if (s != null && s.length() > 0)
                        return s.toString();
                    else
                        return null;
                }
                boolean eol = false;
                char c = 0;
                int i;

                /* Skip a leftover '\n', if necessary */
                if (omitLF && (cb[nextChar] == '\n'))
                    nextChar++;
                skipLF = false;
                omitLF = false;

                charLoop:
                for (i = nextChar; i < nChars; i++) {
                    c = cb[i];
                    if ((c == '\n') || (c == '\r')) {
                        eol = true;
                        break charLoop;
                    }
                }

                startChar = nextChar;
                nextChar = i;

                if (eol) {
                    String str;
                    if (s == null) {
                        str = new String(cb, startChar, i - startChar);
                    } else {
                        s.append(cb, startChar, i - startChar);
                        str = s.toString();
                    }
                    nextChar++;
                    if (c == '\r') {
                        skipLF = true;
                    }
                    return str;
                }

                if (s == null)
                    s = new StringBuffer(defaultExpectedLineLength);
                s.append(cb, startChar, i - startChar);
            }
        }
    }

    //读取一行数据。不忽略换行符
    public String readLine() throws IOException {
        return readLine(false);
    }

    public boolean ready() throws IOException {
        synchronized (lock) {       //同步方法
            ensureOpen();

            if (skipLF) {   //是否忽略换行符
                if (nextChar >= nChars && in.ready()) {
                    fill();
                }
                if (nextChar < nChars) {
                    if (cb[nextChar] == '\n')   //则判断下一个符号是否是换行符,若是的话,则忽略
                        nextChar++;
                    skipLF = false;
                }
            }
            return (nextChar < nChars) || in.ready();
        }
    }

    /**
     * JDK1.8新支持,可以用来读取多行文件,其返回的是一个Stream
     * Stream为JDK1.8的新特性。
     * @return
     */
    public Stream<String> lines() {
        Iterator<String> iter = new Iterator<String>() { //迭代器的实现
            String nextLine = null;

            @Override
            public boolean hasNext() {
                if (nextLine != null) {
                    return true;
                } else {
                    try {
                        nextLine = readLine();
                        return (nextLine != null);
                    } catch (IOException e) {
                        throw new UncheckedIOException(e);
                    }
                }
            }

            @Override
            public String next() {
                if (nextLine != null || hasNext()) {
                    String line = nextLine;
                    nextLine = null;
                    return line;
                } else {
                    throw new NoSuchElementException();
                }
            }
        };

        //通过实现的迭代器返回一个Stream对象
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
                iter, Spliterator.ORDERED | Spliterator.NONNULL), false);
    }
}



如果仔细看的话,可以发现,和上一盘文章中的缓冲字节流在实现上没有什么太大的区别,另外值得注意的就是,在JDK1.8引入了新的功能,也就是lines() 方法,通过该方法可以获取输入流文件的Stream,然后通过对Stream的操作获取文件内容。

上述代码中的fill()方法的详解可以参考文章

http://blog.youkuaiyun.com/asivy/article/details/18704449

再来看看CharArrayReader

/**
 * CharArrayReader实际上是通过“字符数组”去保存数据。
 */
public class CharArrayReader extends Reader {

    protected char buf[];   //字符缓冲区

    protected int pos;      //buf中下一个要被读取的字符位置

    protected int markedPos = 0;    //buf中被mark的字符下标

    protected int count;   //此缓冲区结束的索引。

    public CharArrayReader(char buf[]) {    //通过一个数组来初始化
        this.buf = buf;                     //初始化缓冲区大小
        this.pos = 0;                       //设置位置
        this.count = buf.length;            //设置最大数量
    }


    public CharArrayReader(char buf[], int offset, int length) {    //通过偏移量来设置初始化
        this.buf = buf;
        this.pos = offset;
        this.count = Math.min(offset + length, buf.length);
        this.markedPos = offset;
    }

    private void ensureOpen() throws IOException {

    }

    public int read() throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (pos >= count)
                return -1;
            else
                return buf[pos++];      //返回缓冲区中的数据
        }
    }

    public int read(char b[], int off, int len) throws IOException {
        synchronized (lock) {
            //也是从缓冲区中读取,然后修改了相应的成员变量
            System.arraycopy(buf, pos, b, off, len);
            pos += len;
            return len;
        }
    }

    //用于判断是否还有下一个可读字符
    public boolean ready() throws IOException {
        synchronized (lock) {
            ensureOpen();
            return (count - pos) > 0;       //实际就是索引的位置是否超出了count的大小
        }
    }

    public void close() {
        buf = null;                     //实际就是清空buf释放内存
    }
}

同样,此源码仍旧让我疑惑的地方在于buf数组是在何处初始化的。

再来看看FilterReader

由于FIlterReader的设计初衷就是为了给所有的Reader包装类用作基类,因而其本身没有太多值得关注的地方,到目前为止,其只有一个子类

PushBackReader可回退的字符流

我们稍微看一下源码如下:

/**
 * 本类是用作所有Reader装饰类的基类,因而本身没有什么太多特别需要注意的地方
 * 其实现几乎都是通过委托其维护的成员变量Reader来实现的,
 * 目前该类只有一个子类就是PushBackReader (可回退字符读)
 */
public abstract class FilterReader extends Reader {

    protected Reader in;

    protected FilterReader(Reader in) { //通过Reader类来初始化
        super(in);
        this.in = in;
    }


    public int read() throws IOException {
        return in.read();
    }

    public int read(char cbuf[], int off, int len) throws IOException {
        return in.read(cbuf, off, len);
    }

    public long skip(long n) throws IOException {
        return in.skip(n);
    }

    public boolean ready() throws IOException {
        return in.ready();
    }

    public boolean markSupported() {
        return in.markSupported();
    }

    public void mark(int readAheadLimit) throws IOException {
        in.mark(readAheadLimit);
    }

    public void reset() throws IOException {
        in.reset();
    }

    public void close() throws IOException {
        in.close();
    }

}

再来看看InputStreamReader

通过继承系统,我们可以知道其,还有一个子类FileReader,我们留到后面再来分析他,首先先看看定义

/**
 * 通过名字应该可以感受出其似乎既有读取字节流(InputStream)的功能,又有读取字符流的功能(Reader)
 * 作用:是字节流与字符流之间的桥梁,能将字节流输出为字符流,并且能为字节流指定字符集,可输出一个个的字符;
 * 当使用字节码初始化该类后,在调用该类的读方法获取的就是字符了
 * 我们的机器只会读字节码,而我们人却很难读懂字节码,所以人与机器交流过程中需要编码解码
 * InputStreamReader及其子类FileReader:(从字节到字符)是个解码过程;
 * InputStreamReader这个解码过程中,最主要的就是StreamDecoder类
 */
public class InputStreamReader extends java.io.Reader {

    private final StreamDecoder sd; //该对象是解码也就是将字节解码成字符的实现类

    public InputStreamReader(InputStream in) {  //构造方法需要一个字节流
        super(in);
        try {
            //初始化解码器
            sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
        } catch (UnsupportedEncodingException e) {
            // The default encoding should always be available
            throw new Error(e);
        }
    }

    public InputStreamReader(InputStream in, String charsetName)    //输入流+编码
            throws UnsupportedEncodingException
    {
        super(in);
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        sd = StreamDecoder.forInputStreamReader(in, this, charsetName); //指定编码的解码器
    }

    public String getEncoding() {       //得到解码器的编码
        return sd.getEncoding();
    }

    public int read() throws IOException {
        return sd.read();               //字节委托给解码器,读出来的是字符
    }

    public int read(char cbuf[], int offset, int length) throws IOException {
        return sd.read(cbuf, offset, length);       //同上
    }

    public boolean ready() throws IOException {
        return sd.ready();
    }

    public void close() throws IOException {
        sd.close();
    }
}

通过对源码的解析,似乎也没有什么特别值得注意的,唯一可能需要深究的就是里面维护的解码对象StreamDecoder了,该对象的作用就是根据指定的编码将字节流转码成字符流。

再来看看PipedReader对象

/**
 * PipedReader和PipedWriter即管道输入流和输出流,主要用于线程间管道通信
 */
public class PipedReader extends Reader {
    boolean closedByWriter = false;     //PipedWriter是否关闭的标记
    boolean closedByReader = false;     //PipedReader是否关闭的标记

    //PipedReader与PipedWriter是否连接的标记
    //它在PipedWriter的connect()连接函数中被设置为true
    boolean connected = false;

    Thread readSide;     // 读取“管道”数据的线程
    Thread writeSide;    // 向“管道”写入数据的线程

    private static final int DEFAULT_PIPE_SIZE = 1024;  //管道的默认大小

    char buffer[];      //缓冲区

    int in = -1;        //下一个写入字符的位置。in==out代表满,说明“写入的数据”全部被读取了。

    int out = 0;        //下一个读取字符的位置。in==out代表满,说明“写入的数据”全部被读取了。

    //构造函数:指定与“PipedReader”关联的“PipedWriter”
    public PipedReader(PipedWriter src) throws IOException {
        this(src, DEFAULT_PIPE_SIZE);
    }

    //构造函数:指定与“PipedReader”关联的“PipedWriter”,以及“缓冲区大小”
    public PipedReader(PipedWriter src, int pipeSize) throws IOException {
        initPipe(pipeSize);
        connect(src);
    }

    //构造函数:默认缓冲区大小是1024字符
    public PipedReader() {
        initPipe(DEFAULT_PIPE_SIZE);
    }

    //构造函数:指定缓冲区大小是pipeSize
    public PipedReader(int pipeSize) {
        initPipe(pipeSize);
    }

    //初始化“管道”:新建缓冲区大小
    private void initPipe(int pipeSize) {
        if (pipeSize <= 0) {
            throw new IllegalArgumentException("Pipe size <= 0");
        }
        buffer = new char[pipeSize];
    }

    // 将“PipedReader”和“PipedWriter”绑定。
    // 实际上,这里调用的是PipedWriter的connect()函数
    public void connect(PipedWriter src) throws IOException {
        src.connect(this);
    }

    // 接收int类型的数据b。
    // 它只会在PipedWriter的write(int b)中会被调用
    synchronized void receive(int c) throws IOException {
        // 检查管道状态
        if (!connected) {
            throw new IOException("Pipe not connected");
        } else if (closedByWriter || closedByReader) {
            throw new IOException("Pipe closed");
        } else if (readSide != null && !readSide.isAlive()) {
            throw new IOException("Read end dead");
        }

        // 获取“写入管道”的线程
        writeSide = Thread.currentThread();

        // 如果“管道中被读取的数据,等于写入管道的数据”时,
        // 则每隔1000ms检查“管道状态”,并唤醒管道操作:若有“读取管道数据线程被阻塞”,则唤醒该线程。
        while (in == out) {
            if ((readSide != null) && !readSide.isAlive()) {
                throw new IOException("Pipe broken");
            }
            /* full: kick any waiting readers */
            notifyAll();
            try {
                wait(1000);
            } catch (InterruptedException ex) {
                throw new java.io.InterruptedIOException();
            }
        }
        if (in < 0) {
            in = 0;
            out = 0;
        }
        buffer[in++] = (char) c;        //将c写入到缓冲字符中
        if (in >= buffer.length) {
            in = 0;
        }
    }

    // 接收字符数组c
    synchronized void receive(char c[], int off, int len)  throws IOException {
        while (--len >= 0) {
            receive(c[off++]);
        }
    }

    // 当PipedWriter被关闭时,被调用
    synchronized void receivedLast() {
        closedByWriter = true;
        notifyAll();
    }

    // 从管道(的缓冲)中读取一个字符,并将其转换成int类型
    public synchronized int read()  throws IOException {
        //判断是否连接
        if (!connected) {
            throw new IOException("Pipe not connected");
        } else if (closedByReader) {
            throw new IOException("Pipe closed");
        } else if (writeSide != null && !writeSide.isAlive()
                && !closedByWriter && (in < 0)) {
            throw new IOException("Write end dead");
        }

        //获取读管道线程
        readSide = Thread.currentThread();
        int trials = 2;
        while (in < 0) {    //如果下一个写入的位置小于0
            if (closedByWriter) {   //判断写是否关闭
                /* closed by writer, return EOF */
                return -1;
            }
            if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {
                throw new IOException("Pipe broken");
            }
            /* might be a writer waiting */
            notifyAll();        //唤醒写
            try {
                wait(1000);     //等待1秒
            } catch (InterruptedException ex) {
                throw new java.io.InterruptedIOException();
            }
        }
        int ret = buffer[out++];    //从缓冲区读出数据
        if (out >= buffer.length) {
            out = 0;
        }
        if (in == out) {
            /* now empty */
            in = -1;
        }
        return ret;
    }
    
    // 从管道(的缓冲)中读取数据,并将其存入到数组b中
    public synchronized int read(char cbuf[], int off, int len)  throws IOException {
        if (!connected) {
            throw new IOException("Pipe not connected");
        } else if (closedByReader) {
            throw new IOException("Pipe closed");
        } else if (writeSide != null && !writeSide.isAlive()
                && !closedByWriter && (in < 0)) {
            throw new IOException("Write end dead");
        }

        if ((off < 0) || (off > cbuf.length) || (len < 0) ||
                ((off + len) > cbuf.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        /* possibly wait on the first character */
        int c = read();
        if (c < 0) {
            return -1;
        }
        cbuf[off] =  (char)c;
        int rlen = 1;
        while ((in >= 0) && (--len > 0)) {
            cbuf[off + rlen] = buffer[out++];
            rlen++;
            if (out >= buffer.length) {
                out = 0;
            }
            if (in == out) {
                /* now empty */
                in = -1;
            }
        }
        return rlen;
    }

    //判断是否还可以读取下一个字符
    public synchronized boolean ready() throws IOException {
        if (!connected) {
            throw new IOException("Pipe not connected");
        } else if (closedByReader) {
            throw new IOException("Pipe closed");
        } else if (writeSide != null && !writeSide.isAlive()
                && !closedByWriter && (in < 0)) {
            throw new IOException("Write end dead");
        }
        if (in < 0) {
            return false;
        } else {
            return true;
        }
    }

    public void close()  throws IOException {
        in = -1;
        closedByReader = true;
    }
}
再来看看Reader的最后一个子类:

StringReader :作用就是提供一个String到Reader的适配器类

/**
 *  StringReader并不常用,因为通常情况下使用String更简单一些。
 *  但是在一些需要Reader作为参数的情况下,就需要将String读入到StringReader中来使用了。
 *
 *  StringReader和StringWrite这两个类将String类适配到了Reader和Writer接口,在StringWriter类实现的过程中,
 *  真正使用的是StringBuffer,前面讲过,StringBuffer是一个可变类,由于Writer类中有许多字符串的操作,
 *  所以这个类用起来比较方便;在StringReader类中只定义一个String类即可,因为只涉及到类的读取,而没有修改等的操作,
 *  不会创建多个字符串而造成资源浪费。
 */
public class StringReader extends java.io.Reader {

    private String str;     //待转化的元素字符串
    private int length;
    private int next = 0;   //下一个读取的字符位置

    //构造参数需要一个待转化的字符串
    public StringReader(String s) {
        this.str = s;               //初始化字符串
        this.length = s.length();   //初始化字符串长度
    }

    public int read() throws IOException {
        synchronized (lock) {
            if (next >= length)
                return -1;
            //String.charAt()  方法返回指定索引处的char值。
            return str.charAt(next++);
        }
    }

    /**
     * 将String中指定位置的字符写入到cbuf的char数组中
     */
    public int read(char cbuf[], int off, int len) throws IOException {
        synchronized (lock) {
            int n = Math.min(length - next, len);
            str.getChars(next, next + n, cbuf, off);
            next += n;
            return n;
        }
    }
    
    //是否有下一个可读的字符   --->  为何直接返回的就是true ?
    public boolean ready() throws IOException {
        synchronized (lock) {
            return true;
        }
    }

    public void close() {
        str = null;
    }
}

最后分析完了Reader的直接子类之后我们再来看看剩下的三个孙子类

分别是

LineNumberReader     看名字就可以知道,这个就是可以返回读取文件的行号
PushBackReader  主要作用就是一个可回退的字符读取流
FileReader                    主要就是一个针对文件的读字符流






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值