• 【java I/O流总结】基于源码比较FileReader和BufferReader


    上一篇博客中,测试分析了FileReader&FileWriter,和BufferWriter&BufferReader之间的性能对比。仅仅只是简单分析。现在我基于源码的角度,来分析BufferReader与FileReader的区别。

    首先在构造函数上

    // FileReader构造函数
    public FileReader(File file) throws FileNotFoundException {
        super(new FileInputStream(file));
    }
    //BufferReader构造函数
    public BufferedReader(Reader in, int sz) {
        super(in);
        if (sz <= 0)
            throw new IllegalArgumentException("Buffer size <= 0");
        this.in = in;
        cb = new char[sz];
        nextChar = nChars = 0;
    }
    public BufferedReader(Reader in) {
        this(in, defaultCharBufferSize);//defaultCharBufferSize = 8192,默认缓冲区大小
    }
    

    因为BufferedReader是对FileReader或者InputStreamReader进行包装,而FileReader的父类是InputStreamReader,所以两者的交集就是InputStreamReader,换句话说二者在构造时,都调用了InputStreamReader的构造函数:

    //InputStreamReader的构造函数
    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);
        }
    }
    

    InputStreamReader还有其它几个重载构造函数,但是它们有一个共性,全都调用了sd = StreamDecoder.forInputStreamReader()方法。用它可以获得一个StreamDecoder对象。

    再看后面的源码:

    //InputStreamReader
    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);
    }
    

    可见InputStreamReader中的read()和read(cbuf, offset, length)操作,实际上都是在StreamDecoder中进行。下面分析一下read()和read(cbuf, offset, length)两种情况下FileReader和BufferReader的区别。

    read()

    逐个读入字符

    FileReader没有重写read()方法,所以相当于直接调用StreamDecoder中的read()。

    而BufferReader重写了read(),如下:

    public int read() throws IOException {
        synchronized (lock) {
            ensureOpen();
            for (;;) {
                if (nextChar >= nChars) {
                    fill();
                    if (nextChar >= nChars)
                        return -1;
                }
                if (skipLF) {
                    skipLF = false;
                    if (cb[nextChar] == '
    ') {
                        nextChar++;
                        continue;
                    }
                }
                return cb[nextChar++];
            }
        }
    }
    

    可以看到read()调用了一个fill()函数。其实精髓地方就在这个fill(),它的作用就是填充输入缓冲区。代码如下:

    /**
     * Fills the input buffer, taking the mark into account if it is valid.
     */
    private void fill() throws IOException {
        int dst;
        if (markedChar <= UNMARKED) {
            /* No mark */
            dst = 0;
        } else {
            /* Marked */
            int delta = nextChar - markedChar;
            if (delta >= readAheadLimit) {
                /* Gone past read-ahead limit: Invalidate mark */
                markedChar = INVALIDATED;
                readAheadLimit = 0;
                dst = 0;
            } else {
                if (readAheadLimit <= cb.length) {
                    /* Shuffle in the current buffer */
                    System.arraycopy(cb, markedChar, cb, 0, delta);
                    markedChar = 0;
                    dst = delta;
                } else {
                    /* 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 = delta;
            }
        }
    
        int n;
        do {
            n = in.read(cb, dst, cb.length - dst);
        } while (n == 0);
        if (n > 0) {
            nChars = dst + n;
            nextChar = dst;
        }
    }
    

    fill()在循环中调用了in.read(cb, dst, cb.length - dst),这实际上就是调用InputStreamReader中的read(cbuf, offset, length),即调用StreamDecoder中的read(cbuf, offset, length)。

    所以,即便是一次读入一个字符,BufferReader依然是走char[]模式,性能比直接调用read()要好。

    read(cbuf, offset, length)

    一次读入一个char数组

    注意我前一个博客中的代码是这样写的:read(cbuf),没有后面两个参数,这不用惊讶,实际上就是调用了read(cbuf, offset, length),因为在顶级父类Reader中,有如下代码:

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

    但是注意,read(cbuf, offset, length)方法在Reader中是抽象方法,这个方法在StreamDecoder中实现。

    根据源码,FileReader没有重写read(cbuf, offset, length)方法,所以相当于调用StreamDecoder中的read(cbuf, offset, length);BufferReader重写了,但也使用了fill()方法进行填充,实质上也是使用了StreamDecoder中的read(cbuf, offset, length)。

    所以,使用FileReader一次读入一个char数组时,只要字符数组的大小设置合理,可以达到BufferReader的缓冲区效果。这样就解释了上篇博客测试结果。

    简单总结:

    • InputStreamReader构造函数会创建StreamDecoder对象,在该对象中进行输入操作;
    • BufferReader,不管是一次读入一个字符,还是一次一个数组,都会调用StreamDecoder中的read(cbuf, offset, length),即都使用了缓冲区;
    • FileReader,逐个读取就调用read(),一次一个数组就调用read(cbuf, offset, length)。

    其它

    StreamDecoder

    StreamDecoder的源码分析可参见这个博客,分析得很好:

    JAVA基础知识之StreamDecoder流

    我这里就不再赘述,只提StreamDecoder中几个关键的方法:

    • read0():逐个读入字符的方法;
    • read(char cbuf[], int offset, int length):调用implRead(cbuf, off, off + len);
    • readBytes():读入底层输入的字节流;
    • implRead():将char[]包装成字符缓冲区,调用readBytes()将数据写入字节缓冲区,然后将字节缓冲区中的字节转码成字符存入字符缓冲区。

    为啥都说OutputStreamWriter和InputStreamReader是字节流和字符流的桥梁,这里貌似能窥见一斑。

    FileWriter与BufferWriter

    输出流的分析我这里就不深入写了,跟输入流大同小异,总结成以下三点:

    • 类似的,OutputStreamWriter在构造函数中创建StreamEncoder对象;
    • FileWriter依然不重写各种write方法;
    • BufferWriter重写了write()和write(cbuf, off, len)方法,实际上两者原理类似,都是将字符或者char数组写入buffer,buffer满了再flush写入文件,然后再继续写入buffer循环往复直到结束(char[]写入buffer,实际上就是用System.arraycopy把一个char[]复制进另一个char[];当然还有一种把string写入char[]的方法,getChars);
  • 相关阅读:
    JAVA网络编程-IP组播
    Centos7安装Node
    Android Studio解决support-annotations版本冲突
    Wordpresss建站笔记:英文模板出现中文如何解决?
    Win10系统下插入耳机前面板无声后面板有声的处理(二)
    webstorm编辑器html浏览器快捷浏览按键图标消失的处理
    近期的感想
    Octet string 解析
    SSH隧道:端口转发功能详解
    uint, not []uint8
  • 原文地址:https://www.cnblogs.com/buptleida/p/12587975.html
Copyright © 2020-2023  润新知