Java IO类库之Reader与Writer

筅森魡賤 提交于 2019-12-01 06:51:55

    前面我们已经大致分析了常用的字节流,现在我们来通过分析两个抽象类Reader和Writer来了解下字符流。

一、字符输入流Reader

    根据JDK源码的注释Reader是字符输入流的抽象接口,它的子类必须实现的方法仅有read(char[], int off, int len)、close()方法,大部分子类都会选择重写某些方法以提供更好的性能或者额外的功能,例如BufferedReader等。子类可以通过重写mark、markSuppot()和reset方法支持输入流标记的功能。

    以下是该类的源码解读:

package java.io;

public abstract class Reader implements Readable, Closeable {

    //对象local主要用于同步针对此流的操作,处于性能考虑,一个字符流可能会使用一个内部的锁对象保护关键代码 
    //块(同步代码块),JDK推荐使用此对象而不是使用当前对象this或者使用synchronized方法
    protected Object lock;

    //构造函数,创建一个字符流Reader,它的同步代码块将使用本身this对象进行同步
    protected Reader() {
        this.lock = this;
    }

    //构造函数,它的同步代码块将使用方法指定的lock对象进行同步
    protected Reader(Object lock) {
        if (lock == null) {
            throw new NullPointerException();
        }
        this.lock = lock;
    }

    /**
     * 尝试读取字符到指定的字符缓冲区 
     * 缓冲区可用作字符的缓冲区,所做的唯一改变是put操作的结果,不对缓冲区执行翻转或者重绕操作
     */
    public int read(java.nio.CharBuffer target) throws IOException {
        //获取缓冲区的剩余字符个数
        int len = target.remaining();
        char[] cbuf = new char[len];
        //从输入流中读取len个字符到字符数组cbuf中,返回实际读取的字符数
        int n = read(cbuf, 0, len);
        //若实际读取的字符数大于0,将实际读取到的字符存入缓冲区target中
        if (n > 0)
            target.put(cbuf, 0, n);
        //返回实际读取的字符个数
        return n;
    }

    /**
     * Reads a single character.  This method will block until a character is
     * available, an I/O error occurs, or the end of the stream is reached.
     *
     * <p> Subclasses that intend to support efficient single-character input
     * should override this method.
     *
     * @return     The character read, as an integer in the range 0 to 65535
     *             (<tt>0x00-0xffff</tt>), or -1 if the end of the stream has
     *             been reached
     *
     * @exception  IOException  If an I/O error occurs
     */
    public int read() throws IOException {
        char cb[] = new char[1];
        if (read(cb, 0, 1) == -1)
            return -1;
        else
            return cb[0];
    }

    /**
     * 将字符输入流中的数据读入到cbuf,该方法会阻塞直到存在新的可读数据,发生IO错误以及到达读取终点
     * 返回实际读取的字符数,或者-1标识已经到达字符输入流末尾
     */
    public int read(char cbuf[]) throws IOException {
        return read(cbuf, 0, cbuf.length);
    }

    /**
     * 从输入流中读取字符数据到指定的字符数组cbuf,方法指定了数组cbuf存储输入流字符数据的起点、和读取的最 
     * 大字符数,返回实际读取的字符个数,-1标识到达输入流末尾
     */
    abstract public int read(char cbuf[], int off, int len) throws IOException;

    /** 保存被跳过字符数据缓存数组长度的最大限制 */
    private static final int maxSkipBufferSize = 8192;

    /** 保存被跳过字符数据的缓存数组 ,初始化为null需要的时候进行分配 **/
    private char skipBuffer[] = null;

    /**
     * 跳过指定字符数. 该方法可能阻塞当跳过的字符中有些字符正在使用,或者发生IO错误,或者输入流读取结束
     */
    public long skip(long n) throws IOException {
        //n小于0抛出IllagalArgumentException非法参数异常
        if (n < 0L)
            throw new IllegalArgumentException("skip value is negative");
        //跳转字符数不能超过最大限制,JDK1.8规定是8192
        int nn = (int) Math.min(n, maxSkipBufferSize);
        synchronized (lock) {
            //若现有保存跳转字符的缓冲区数组skipBuffer为空或者长度小于指定跳转字符数则为他重新分配一个
            //长度为指定跳转字符数大小的char数组
            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;
        }
    }

    /**
     * 检测当前输入流是否可读
     * 如果下一次read不会被阻塞则返回true否则返回false,注意返回false也不是说下次read一定阻塞
     */
    public boolean ready() throws IOException {
        return false;
    }

    /**
     * 返回本类是否支持标记,支持返回true不支持返回false,默认实现返回false,子类如果支持标记需要重写该 
     *方法
     */
    public boolean markSupported() {
        return false;
    }

    /**
     * 标记输入流的当前位置.之后调用reset将会重定位到当前位置重新开始读取.不是所有的字符输入流都支持mark 
     * 方法,readAheadLimit参数用于指定标记之后到可以调用reset方法重置输入流的字符数,当上一次标记位置 
     * 之后继续读取超过该限制的字符数据尝试通过调用reset回滚到上次标记位置重新读取将会失败
     * 发生IO错误或者该字符输入流不支持mark操作方法抛出IOException异常
     */
    public void mark(int readAheadLimit) throws IOException {
        throw new IOException("mark() not supported");
    }

    /**
     * 重置输入流. 如果该输入流被标记, 那么尝试重新定位到之前标记的位置重新读取之后的数据.
     * 如果输入流没有标记过,那么根据不同的实现类,会进行不同的重置处理,例如重定位当前读取位置到输入流初始 
     * 位置.并不是所有的字符输入流都支持reset方法,一些支持reset操作的可能也不支持标记操作mark.
     * 当输入流未曾标记或者标记无效或者输入流不支持reset方法或者发生其他的IO错误都会抛出IO异常                       
     */
    public void reset() throws IOException {
        throw new IOException("reset() not supported");
    }

    /**
     * 关闭输入流释放与之相关的系统资源,当输入流关闭之后,调用read、ready、mark、reset或者skip方法都 
     *将抛出IOException异常,调用close方法关闭之前已经关闭的输入流不会有任何影响。
     */
     abstract public void close() throws IOException;

}

   

二、字符输出流Writer

    Writer是字符输出流的抽象接口,它的子类必须实现的方法包括write(char[], int off, int len),flush()和close()方法。大部分子类都会选择重写某些方法已提供更好的性能或者额外的方法。

    以下是该类的源码解读:

package java.io;

public abstract class Writer implements Appendable, Closeable, Flushable {

    /**
     * 字符缓冲数组用于临时存放将要写入到输出流中的字符
     */
    private char[] writeBuffer;

    /**
     * 字符缓冲数组的容量
     */
    private static final int WRITE_BUFFER_SIZE = 1024;

    /**
     * 锁对象用于同步针对该输出流的操作.处于性能考虑,字符输出流对象可能会使用一个锁对象而不是对它本身加锁去保护关键 
     * 代码块(同步代码块)。子类应该使用这个锁对象而不是Writer对象本身或者同步方法
     */
    protected Object lock;

    /**
     * 构造函数。它关键部分的代码(需要同步的代码块)将会选择他本身作为锁对象进行同步
     */
    protected Writer() {
        this.lock = this;
    }

    /**
     * 构造函数。它的关键部分代码(同步代码块)将使用方法指定的锁对象进行同步
     */
    protected Writer(Object lock) {
        if (lock == null) {
            throw new NullPointerException();
        }
        this.lock = lock;
    }

    /**
     * 写入单个字符,要写入的字符包含在给定整数值的低16位中,高16位被忽略
     * 子类若想实现高效的单字符写入方法可重写
     */
    public void write(int c) throws IOException {
        synchronized (lock) {
            if (writeBuffer == null){
                writeBuffer = new char[WRITE_BUFFER_SIZE];
            }
            writeBuffer[0] = (char) c;
            write(writeBuffer, 0, 1);
        }
    }

    /**
     * 尝试将一个字节数组写入到输出流中
     */
    public void write(char cbuf[]) throws IOException {
        write(cbuf, 0, cbuf.length);
    }

    /**
     * 尝试将一个数组从off开始的len个字符写入到输出流中,可能写入的字符数小于len个
     */
    abstract public void write(char cbuf[], int off, int len) throws IOException;

    /**
     * 写入一个字符串
     */
    public void write(String str) throws IOException {
        write(str, 0, str.length());
    }

    /**
     * 试图将字符串的一部分,从off开始的len个字符写入到输出流中
     * 尝试写入len个字符,但写入的字符数可能低于len个
     */
    public void write(String str, int off, int len) throws IOException {
        synchronized (lock) {
            char cbuf[];
            if (len <= WRITE_BUFFER_SIZE) {
                if (writeBuffer == null) {
                    writeBuffer = new char[WRITE_BUFFER_SIZE];
                }
                cbuf = writeBuffer;
            } else {    // Don't permanently allocate very large buffers.
                cbuf = new char[len];
            }
            str.getChars(off, (off + len), cbuf, 0);
            write(cbuf, 0, len);
        }
    }

    /**
     * 将指定的字符序列写入到字符输出流末尾。与调用out.write的效果完全相同。因为调用该方法实际写入输出流的内容其实是
     * csq.toString方法返回的内容,所以整个字符序列能否被全部被写进去取决于指定字符序列csq的toString方法实现,
     * 例如调用一个CharacterBuffer的toString方法,方法返回的内容取决于它当前的读取位置和保存的字符个数。当csq为
     * null时会往输入流写入“null”四个字符
     */
    public Writer append(CharSequence csq) throws IOException {
        if (csq == null)
            write("null");
        else
            write(csq.toString());
        return this;
    }

    /**
     * 将指定字符序列的一部分(start位置开始到end位置不包括end位置字符)追加到该字符输出流末尾,
     */
    public Writer append(CharSequence csq, int start, int end) throws IOException {
        CharSequence cs = (csq == null ? "null" : csq);
        write(cs.subSequence(start, end).toString());
        return this;
    }

    /**
     * 往输出流追加指定的字符
     */
    public Writer append(char c) throws IOException {
        write(c);
        return this;
    }

    /**
     * 刷新该流的缓冲区.如果缓冲数组保存了write方法写入的字符数据,那么立刻将他们写入到目标对象,目标对象可能是绑定的 
     * 另一个字符输出流或者字节输出流,该方法将会刷新绑定的所有字符和字节输出流的缓冲区
     * 如果写入的目标是一个File对象那么该输出流仅保证先前写入流的数据传递到操作系统进行写入,但是不保证立即写入物理设 
     * 备中
     */
    abstract public void flush() throws IOException;

    /**
     * 关闭当前输出流方法,抽象方法留给子类实现, 建议关闭流之前先调用刷新函数flush刷新缓存.一旦该输出流关闭后续调用 
     * write和flush方法都应该抛出一个IO异常。重复关闭一个字符输出流不会有问题
     */
    abstract public void close() throws IOException;

}

 

三、字符流和字节流的区别

1 - Reader和InputStream

    1)操作的对象不同,Reader操作的是字符,InputStream是字节;

    2)Reader默认实现了一个Readable接口,比InputStream多提供了一个将输入流中的字符数据读取到指定字符缓存CharBuffer的方法(read(java.nio.CharBuffer));

    3)Reader的close方法是抽象的子类必须实现,而InputStream的close方法不是。

2 - Writer和OutputStream

    1)Write操作的是字符,OutputStream操作的是字节;

    2)实现的接口不同,Writer相比OutputStream多实现了Appendable接口,提供了几个在输出流中写入单个字符、字符序列的方法;

    3)Writer的close、flush方法是抽象的,子类必须实现,而OutputStream的close、flush方法不是;

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!