BufferedOutputStream的缓存功能解析(源码阅读)

要介绍BufferedOutputStream,我们先了解一下OutputStream类

抽象类OutputStream类有三个write方法

  1. public abstract void write(int b)
  2. public void write(byte b[])
  3. public void write(byte b[], int off, int len)

由上面我们可以看出第一个write方法是让子类覆盖的,而第二个人write(byte b[])方法源代码如下

public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

所以可见最后处理还是调用第三个方法write(byte b[],int off,int len),该方法源码如下:

 public void write(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
                   ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        for (int i = 0 ; i < len ; i++) {
        //注意这儿,这儿其实调用前面的抽象方法write(int b),同时还发生了自动转型
            write(b[off + i]);
        }
    }

问题

我们先不看抽象方法是如何实现的,也就是说OutputStream也具有缓存器功能,我们可以将要写入到流中的数据写到一个byte[] buf数组中,然后调用write(byte b[])或者write(byte b[], int off, int len)也可以,那为什么还要BufferedInputStream类干什么呢,他们有什么区别呢。同时我们知道BufferedInputStream类中还有一个flush()方法,在OutputStream流中没有flush()方法,这又是为什么呢?flush()是不是必须的呢,接下来看一下BufferedOutputStream类;



首先,BufferedOutput将OutputStream类对象作为一个构造方法的参数的。

首先看一下BufferedOutputStream 类源代码

public
class BufferedOutputStream extends FilterOutputStream {

    //这儿定义了一个byte[]数组,用来充当缓存器
    protected byte buf[];
    //这个变量是重点,他就是用来记录当前缓存器中的字节数量的
    protected int count;
    //我们初始化创建一个对象的时候给了这个buf这个数组8192个字节.
    public BufferedOutputStream(OutputStream out) {
        this(out, 8192);
    }

    public BufferedOutputStream(OutputStream out, int size) {
        super(out);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
      // 这儿创建一个给定大小的数组对象来充当缓存器
        buf = new byte[size];
    }

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

//该方法是重点
public synchronized void write(byte b[], int off, int len) throws IOException {
        //如果传进来的数组长度大于buf 数组的长度,则直接调用OutputStream对象的write方法。
        if (len >= buf.length) {

            flushBuffer();
            out.write(b, off, len);
            return;
        }
        //验证BufferedOutputStream 类中buf剩下的空间能否装得下传进来的数组。如果不能则先将当前buf数组中数据写入底层io流中
        if (len > buf.length - count) {
            flushBuffer();
        }
        //该处是重点,如果在当前BufferedOutputStream 类中buf数组没有满,则将传进来的数组复制到当前类对象buf数组中,同时更新count的值。
        System.arraycopy(b, off, buf, count, len);
        count += len;
   //调用flushBuffer方法也就是将不满8192个字节数组中的数据发送出去。同时将count置零。
    private void flushBuffer() throws IOException {
        if (count > 0) {
            out.write(buf, 0, count);
            count = 0;
        }
    }

    //强制将buf数据中未满8192个字节的数据写入底层io中。
    public synchronized void flush() throws IOException {
        flushBuffer();
        out.flush();
    }
}


结论:

OutputStream的缓存器(数组)与BufferedOutputStream中类的缓存器(数组)本质是一样的,只是BufferedOutputStream类中将要写入到底层io流中的数据先凑个整,然后再一起写入底层io流中,这样就大大节省了io操作,大大提高了io利用率,写一次io是很费资源的。这样也出现了一个问题,假设向硬盘中写入一个文件,文件最后数据比默认值8192个字节小,则BufferOutputStream就不会将这些数据写入底层io流中,造成文件缺失,因此就需要在close()前调用flush()方法,强制将还没有装满buf数组的数据写入底层io中。同时也可以看出节点流是不用flush()方法的,而一般的处理流都会采用固定buf这种方式的,比如常用的PrintWriter里面其实操作的就是一个BufferedWriter对象,因此也需要调用flush()方法来刷新,因为默认是不刷新的。

参考:

http://www.bdqn.cn/news/201311/12111.shtml

时间: 2024-08-30 04:16:37

BufferedOutputStream的缓存功能解析(源码阅读)的相关文章

【第六篇】Volley代码修改之图片二级缓存以及相关源码阅读(重现ImageLoader.ImageCache)

前面http://www.cnblogs.com/androidsuperman/p/8a157b18ede85caa61ca5bc04bba43d0.html 有讲到使用LRU来处理缓存的,但是只是处理内存里面的缓存,没进行文件缓存和处理,那么如何实现Volley在本地的缓存呢 一般硬盘缓存使用com.jakewharton.disklrucache.DiskLruCache这个Lru缓存,具体代码在 https://github.com/JakeWharton/DiskLruCache/tr

【jbpm4.4源码阅读笔记】engine的解析与生成

jpbm4.4源码的包结构主要有七个,分为org.jbpm.api;org.jbpm.bpmn;org.jbpm.enterprise.internal;org.jbpm.internal.log;org.jbpm.jpdl.internal;pvm.internal; 简而言之,api为接口,比如service.dao等的接口,bpmn定义了jbpm模型,比如task.end等节点的属性和动作,pvm即工作流虚拟机,是jbpm的核心实现:jpdl则是java process define la

[Apache Spark源码阅读]天堂之门——SparkContext解析

稍微了解Spark源码的人应该都知道SparkContext,作为整个Project的程序入口,其重要性不言而喻,许多大牛也在源码分析的文章中对其做了很多相关的深入分析和解读.这里,结合自己前段时间的阅读体会,与大家共同讨论学习一下Spark的入口对象—天堂之门—SparkContex. SparkContex位于项目的源码路径\spark-master\core\src\main\scala\org\apache\spark\SparkContext.scala中,源文件包含Classs Sp

iOS开发- 自定义遮罩视图(引导, 功能说明)源码+解析

iOS开发- 自定义遮罩视图(引导, 功能说明)源码+解析 我们平时使用App的时候, 经常在第一次使用的时候, 会有类似"新手教程"之类的东西, 来引导我们应该如何使用这个App. 但是这个"新手教程"不同于常规的引导页(引导页指第一次打开App时候, 弹出的那种介绍视图. 他是静态的, 不需要与用户交互, 可以直接一页页翻, 或者直接跳过.)所谓的"新手教程", 就是按照App的提示, 一步步跟着完成. 那这个"新手教程"

SpringMVC源码阅读:视图解析器

1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC如何完成视图解析的 2.源码分析 在SpringMVC源码阅读:拦截器分析过doDispatch的运行过程,这里再分析一遍 回到DispatcherServlet类的doDispatch方法,看看doDispatch如何获取ModelAndView Hand

SpringMVC源码阅读:异常解析器

1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC如何完成异常解析.捕捉异常,并自定义异常和异常解析器 2.源码分析 进入DispatcherServlet的processDispatchResult方法 1024行判断异常是否是ModelAndViewDefiningException类型,如果是,直接返

JDK源码阅读(三):ArraryList源码解析

今天来看一下ArrayList的源码 目录 介绍 继承结构 属性 构造方法 add方法 remove方法 修改方法 获取元素 size()方法 isEmpty方法 clear方法 循环数组 1.介绍 一般来讲文章开始应该先介绍一下说下简介.这里就不介绍了 如果你不知道ArrayList是什么的话就没必要在看了.大致讲一下一些常用的方法 2.继承结构 ArrayList源码定义: ArrayList继承结构如下: Serializable 序列化接口 Cloneable 前面我们在看Object源

ThinkPHP源码阅读3------行为扩展

ThinkPHP的核心采用的框架模式是CBD,也就是核心Core+行为Behavior+驱动Driver,核心也就是整个框架模式的核心,大部分都是一些基类,去规定规则,Behavior是行为,就是在指定的地方调用来完成一些特定的行为功能,而Driver驱动就类似cache缓存驱动,mysqldb 数据库驱动等,完成功能 行为在手册的13.1中说的很详细.我只去写一些实例.调用行为的方法. 调用方法是tags() /** * 处理标签扩展 * @param string $tag * 标签名称 *

【原】FMDB源码阅读(一)

[原]FMDB源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于FMDB组件我是一点都没用过.好在FMDB源码中的main.m文件提供了大量的示例,况且网上也有很多最佳实践的例子,我就不在这献丑了.我们先从一个最简单的FMDB的例子开始: // 找到用户目录下的Documents文件夹位置 NSString* docsdir = [NSSearchPathFor