栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java自学日记之IO流(三):缓冲流(BufferedInputStream、BufferedOutputStream)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java自学日记之IO流(三):缓冲流(BufferedInputStream、BufferedOutputStream)

系列文章目录

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


文章目录

系列文章目录前言一、缓冲流的实现原理与作用二、缓冲流构造方法三、read()方法四、write()方法总结


前言

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


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

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

private static int DEFAULT_BUFFER_SIZE = 8192;

当空间不够时,
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)

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

public BufferedOutputStream(OutputStream out) {
    this(out, 8192);
}

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

public BufferedOutputStream(OutputStream out, int size) {
    super(out);
    if (size <= 0) {
        throw new IllegalArgumentException("Buffer size <= 0");
    }
    buf = new byte[size];
}

父类

public FilterOutputStream(OutputStream out) {
    this.out = out;
}

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

三、read()方法

read经典两个构造函数

public synchronized int read() throws IOException
public synchronized int read(byte b[], int off, int len)
        throws IOException

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

public synchronized int read() throws IOException {
    if (pos >= count) {
        fill();
        if (pos >= count)
            return -1;
    }
    return getBufIfOpen()[pos++] & 0xff;
}

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;
    }
}

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

private int read1(byte[] b, int off, int len) throws IOException {
    int avail = count - pos;
    if (avail <= 0) {
        
        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;
}

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

如果没有标记位置且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)

先看第一个

public synchronized void write(int b) throws IOException {
    if (count >= buf.length) {
        flushBuffer();
    }
    buf[count++] = (byte)b;
}

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

private void flushBuffer() throws IOException {
    if (count > 0) {
        out.write(buf, 0, count);
        count = 0;
    }
}

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

接下来看write第二种构造

public synchronized void write(byte b[], int off, int len) throws IOException {
    if (len >= buf.length) {
        
        flushBuffer();
        out.write(b, off, len);
        return;
    }
    if (len > buf.length - count) {
        flushBuffer();
    }
    System.arraycopy(b, off, buf, count, len);
    count += len;
}

如果需要写的数据长度已经超出容器的长度,则直接写到相应的outputStream中,清空缓冲区 ,并返回否则,如果需要写的数据长度大于buf剩余可写的空间时,清空缓冲区,最后将数组b拷贝给buf并标记有效长度


总结

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/759166.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号