当前位置:   article > 正文

Java自学日记之IO流(三):缓冲流(BufferedInputStream、BufferedOutputStream)_bufferedinputstream哪个方法是添加缓存的

bufferedinputstream哪个方法是添加缓存的

系列文章目录

Java自学日记之IO流(一):字节流和字符流
Java自学日记之IO流(二):转换流(InputStreamReader、OutputStreamWriter)



前言

前文讲了转换流,建立了字节流和字符流之间的沟通,接下来我们要考虑的就是如何更高效地读入写入文件,这里就引入了缓冲流


一、缓冲流的实现原理与作用

将读入输出流套上缓冲流之后,缓冲流会把数据存进一个大小为 capacity = 8192 = 8KB 的内部空间中

private static int DEFAULT_BUFFER_SIZE = 8192;
  • 1

当空间不够时,
BufferedInputStream会重新对缓冲区填充数据,
BufferedOutputStream会调用flushBuffer(), 把缓冲区的数据写入对应的outputStream中, 然后将缓冲区清空重新读入

本质上都是提供了一个数据缓存的功能,这样最大的好处是减少了磁盘的操作次数。原始的InputStream类实现的read是即时读取的,即每一次读取都会是一次磁盘IO操作(哪怕只读取了1个字节的数据),而通过缓冲区的实现,读取可以读取缓冲区中的内容,当读取超过缓冲区的内容后再进行一次磁盘IO,载入一段数据填充缓冲,大幅减少了减少了磁盘IO的频率。下面聊具体实现


二、缓冲流构造方法

BufferedInputStream,BufferedOutputStream分别有两种构造方式,都是比较简单的套接一个原始的读取输入流(InputStream、OutputStream)

public BufferedInputStream(InputStream in)
public BufferedInputStream(InputStream in, int size)

public BufferedInputStream(OutputStream out)
public BufferedInputStream(OutputStream out, int size)
  • 1
  • 2
  • 3
  • 4
  • 5

单参数的构造会在内部调用双参数的构造

public BufferedOutputStream(OutputStream out) {
    this(out, 8192);
}
  • 1
  • 2
  • 3

双参数的构造会调用其父类(FilterInputStream、FilterOutputStream)的构造

public BufferedOutputStream(OutputStream out, int size) {
    super(out);
    if (size <= 0) {
        throw new IllegalArgumentException("Buffer size <= 0");
    }
    buf = new byte[size];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

父类

public FilterOutputStream(OutputStream out) {
    this.out = out;
}
  • 1
  • 2
  • 3

所以最后构造函数会把原始流存储在FilterInputStream/FilterOutputStream中

三、read()方法

read经典两个构造函数

public synchronized int read() throws IOException
public synchronized int read(byte b[], int off, int len)
        throws IOException
  • 1
  • 2
  • 3

一个直接调用内部的buf数组返回一个字节(这里就能看出和原始流的区别了,原始流直接从磁盘文件读取一个字符,而BufferedInputStream则从内部缓冲区读取)

public synchronized int read() throws IOException {
    if (pos >= count) {
        fill();
        if (pos >= count)
            return -1;
    }
    return getBufIfOpen()[pos++] & 0xff;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

getBufIfOpen()返回buf数组,所以这个read函数返回 buf[pos] & 0xff
至于为什么 & 0xff ,感兴趣的可以去看看这篇博客
byte为什么要与上0xff?

另外一个read是给个数组,要求读取len个字节,除非读到了流的末尾,否则不断调用内部类read1读取

public synchronized int read(byte b[], int off, int len)
	throws IOException
{
    getBufIfOpen(); // Check for closed stream
    if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
        throw new IndexOutOfBoundsException();
    } else if (len == 0) {
        return 0;
    }

    int n = 0;
    for (;;) {
        int nread = read1(b, off + n, len - n);
        if (nread <= 0)
            return (n == 0) ? nread : n;
        n += nread;
        if (n >= len)
            return n;
        // if not closed but no bytes available, return
        InputStream input = in;
        if (input != null && input.available() <= 0)
            return n;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

不难看出,当read1(b, off + n, len - n)返回值<=0 或者 读取长度满足len 或者 no bytes available 时,可以返回,后两个都好理解,read1什么情况下会返回小于等于零呢,继续跟进

/**
 - Read characters into a portion of an array, reading from the underlying
 - stream at most once if necessary.
 */
private int read1(byte[] b, int off, int len) throws IOException {
    int avail = count - pos;
    if (avail <= 0) {
        /* If the requested length is at least as large as the buffer, and
           if there is no mark/reset activity, do not bother to copy the
           bytes into the local buffer.  In this way buffered streams will
           cascade harmlessly. */
        if (len >= getBufIfOpen().length && markpos < 0) {
            return getInIfOpen().read(b, off, len);
        }
        //当无数据可读时,从原始流中载入数据 
        fill();
        avail = count - pos;
        if (avail <= 0) return -1;
    }
    int cnt = (avail < len) ? avail : len;
    System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
    pos += cnt;
    return cnt;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

当读取的长度大于缓冲区的长度时:

  • 如果没有标记位置且len请求长度大于buf最大长度,则直接从原始输入流中进行读取
  • 否则再次从原始流缓存数据,再次缓存的数据重新计算avail=count(目前缓冲区域中有多少有效的字节)- pos(当前缓冲区的读取位置),如果avail小于零,说明当前没有能传输的量的数据量,返回-1

四、write()方法

这里要注意的是,这里的write是先往缓冲区里写,如果缓冲区满了,再输出
有两个write函数

public synchronized void write(int b) throws IOException
public synchronized void write(byte b[], int off, int len)
  • 1
  • 2

先看第一个

public synchronized void write(int b) throws IOException {
    if (count >= buf.length) {
        flushBuffer();
    }
    buf[count++] = (byte)b;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

当buf中有效字节数>=buf长度时(其实应该就是等于),flushBuffer()
flushBuffer () 这个方法干了什么呢, 继续跟进

/** Flush the internal buffer */
private void flushBuffer() throws IOException {
    if (count > 0) {
        out.write(buf, 0, count);
        count = 0;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

调用了一个write方法,输出buf里全部内容,并将count归零

接下来看write第二种构造

public synchronized void write(byte b[], int off, int len) throws IOException {
    if (len >= buf.length) {
        /* If the request length exceeds the size of the output buffer,
           flush the output buffer and then write the data directly.
           In this way buffered streams will cascade harmlessly. */
        flushBuffer();
        out.write(b, off, len);
        return;
    }
    if (len > buf.length - count) {
        flushBuffer();
    }
    System.arraycopy(b, off, buf, count, len);
    count += len;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 如果需要写的数据长度已经超出容器的长度,则直接写到相应的outputStream中,清空缓冲区 ,并返回
  • 否则,如果需要写的数据长度大于buf剩余可写的空间时,清空缓冲区,最后将数组b拷贝给buf并标记有效长度

总结

嗯,缓冲流差不多就这些了,明天会写字节数组流,并将它与缓冲流做一个对比

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/146837
推荐阅读
相关标签
  

闽ICP备14008679号