BufferedInputStream是一个带有缓冲区域的InputStream,它的继承体系如下: 



InputStream 

|__FilterInputStream 

        |__BufferedInputStream 



首先了解一下FilterInputStream: 

FilterInputStream通过装饰器模式将InputStream封装至内部的一个成员变量

  1. protected volatile InputStream in;

需要注意的是该成员变量使用了volatile关键字进行修饰,这意味着该成员变量的引用的内存可见性为多线程即时可见的。 

其它地方FilterInputStream将所有的操作委托给了in这个成员进行操作。 



了解了这些过后,来仔细看看BufferedInputStream的成员变量:

  1. //该变量定义了默认的缓冲大小
  2. protected volatile byte buf[]; //缓冲数组,注意该成员变量同样使用了volatile关键字进行修饰,作用为在多线程环境中,当对该变量引用进行修改时保证了内存的可见性。
  3. private static final AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater = AtomicReferenceFieldUpdater.newUpdater(BufferedInputStream.class,  byte[].class, "buf")//缓存数组的原子更新器,该成员变量与buf数组的volatile关键字共同组成了buf数组的原子更新功能实现。
  4. protected int count;//该成员变量表示目前缓冲区域中有多少有效的字节。
  5. protected int pos;//该成员变量表示了当前缓冲区的读取位置。
  6. ;/*表示标记位置,该标记位置的作用为:实现流的标记特性,即流的某个位置可以被设置为标记,允许通过设置reset(),将流的读取位置进行重置到该标记位置,但是InputStream注释上明确表示,该流不会无限的保证标记长度可以无限延长,即markpos=15,pos=139734,该保留区间可能已经超过了保留的极限(如下)*/
  7. protected int marklimit;/*该成员变量表示了上面提到的标记最大保留区间大小,当pos-markpos> marklimit时,mark标记可能会被清除(根据实现确定)。*/

通过构造函数可以看到:初始化了一个byte数组作为缓冲区域

  1. public BufferedInputStream(InputStream in, int size) {
  2. super(in);
  3. ) {
  4. throw new IllegalArgumentException("Buffer size <= 0");
  5. }
  6. buf = new byte[size];
  7. }

这个类中最为重要的方法是fill()方法,它提供了缓冲区域的读取、写入、区域元素的移动更新等。下面着重分析一下该方法:

  1. private void fill() throws IOException {
  2. byte[] buffer = getBufIfOpen();
  3. ) {
  4. /*如果不存在标记位置(即没有需要进行reset的位置需求)
  5. 则可以进行大胆地直接重置pos标识下一可读取位置,但是这样
  6. 不是会读取到以前的旧数据吗?不用担心,在后面的代码里☆会实现输入流的新
  7. 数据填充*/
  8. ;
  9. }else if (pos >= buffer.length){
  10. /* 位置大于缓冲区长度,这里表示已经没有可用空间了 */
  11. ) {
  12. /* 表示存在mark位置,则要对mark位置到pos位置的数据予以保留,
  13. 以确保后面如果调用reset()重新从mark位置读取会取得成功*/
  14. int sz = pos - markpos;
  15. /*该实现是通过将缓冲区域中markpos至pos部分的移至缓冲区头部实现*/
  16. , sz);
  17. pos = sz;
  18. ;
  19. } else if (buffer.length >= marklimit) {
  20. /* 如果缓冲区已经足够大,可以容纳marklimit,则直接重置*/
  21. ;
  22. ;/* 丢弃所有的缓冲区内容 */
  23. } else {
  24. /* 如果缓冲区还能增长的空间,则进行缓冲区扩容*/
  25. ;
  26. /*新的缓冲区大小设置成满足最大标记极限即可*/
  27. if (nsz > marklimit)
  28. nsz = marklimit;
  29. byte nbuf[] = new byte[nsz];
  30. //将原来的较小的缓冲内容COPY至增容的新缓冲区中
  31. , nbuf, 0, pos);
  32. //这里使用了原子变量引用更新,确保多线程环境下内存的可见性
  33. if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
  34. // Can't replace buf if there was an async close.
  35. // Note: This would need to be changed if fill()
  36. // is ever made accessible to multiple threads.
  37. // But for now, the only way CAS can fail is via close.
  38. // assert buf == null;
  39. throw new IOException("Stream closed");
  40. }
  41. buffer = nbuf;
  42. }
  43. count = pos;
  44. //从原始输入流中读取数据,填充缓冲区
  45. int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
  46. //根据实际读取的字节数更新缓冲区中可用字节数
  47. )
  48. count = n + pos;
  49. }

整个fill的过程,可以看作是BufferedInputStream对外提供滑动读取的功能实现,通过预先读入一整段原始输入流数据至缓冲区中,而外界对BufferedInputStream的读取操作实际上是在缓冲区上进行,如果读取的数据超过了缓冲区的范围,那么BufferedInputStream负责重新从原始输入流中载入下一截数据填充缓冲区,然后外界继续通过缓冲区进行数据读取。这样的设计的好处是:避免了大量的磁盘IO,因为原始的InputStream类实现的read是即时读取的,即每一次读取都会是一次磁盘IO操作(哪怕只读取了1个字节的数据),可想而知,如果数据量巨大,这样的磁盘消耗非常可怕。而通过缓冲区的实现,读取可以读取缓冲区中的内容,当读取超过缓冲区的内容后再进行一次磁盘IO,载入一段数据填充缓冲,那么下一次读取一般情况下就直接可以从缓冲区读取,减少了磁盘IO。减少的磁盘IO大致可以通过以下方式计算(限read()方式): 



length  流的最终大小 

bufSize 缓冲区大小 



则通过缓冲区实现的输入流BufferedInputStream的磁盘IO数为原始InputStream磁盘IO的 

1/(length/bufSize) 



read方法解析:该方法返回当前位置的后一位置byte值(int表示).

  1. public synchronized int read() throws IOException {
  2. if (pos >= count) {
  3. /*表示读取位置已经超过了缓冲区可用范围,则对缓冲区进行重新填充*/
  4. fill();
  5. /*当填充后再次读取时发现没有数据可读,证明读到了流末尾*/
  6. if (pos >= count)
  7. ;
  8. }
  9. /*这里表示读取位置尚未超过缓冲区有效范围,直接返回缓冲区内容*/
  10. return getBufIfOpen()[pos++] & 0xff;
  11. }

一次读取多个字节(尽量读,非贪婪)

  1. private int read1(byte[] b, int off, int len) throws IOException {
  2. int avail = count - pos;
  3. ) {
  4. /*这里使用了一个巧妙的机制,如果读取的长度大于缓冲区的长度
  5. 并且没有markpos,则直接从原始输入流中进行读取,从而避免无谓的
  6. COPY(从原始输入流至缓冲区,读取缓冲区全部数据,清空缓冲区,
  7. 重新填入原始输入流数据)*/
  8. ) {
  9. return getInIfOpen().read(b, off, len);
  10. }
  11. /*当无数据可读时,从原始流中载入数据到缓冲区中*/
  12. fill();
  13. avail = count - pos;
  14. ) return -1;
  15. }
  16. int cnt = (avail < len) ? avail : len;
  17. /*从缓冲区中读取数据,返回实际读取到的大小*/
  18. System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
  19. pos += cnt;
  20. return cnt;
  21. }

以下方法和上面的方法类似,唯一不同的是,上面的方法是尽量读,读到多少是多少,而下面的方法是贪婪的读,没有读到足够多的数据(len)就不会返回,除非读到了流的末尾。该方法通过不断循环地调用上面read1方法实现贪婪读取。

  1. public synchronized int read(byte b[], int off, int len)
  2. throws IOException
  3. {
  4. getBufIfOpen(); // Check for closed stream
  5. ) {
  6. throw new IndexOutOfBoundsException();
  7. ) {
  8. ;
  9. }
  10. ;
  11. for (;;) {
  12. int nread = read1(b, off + n, len - n);
  13. )
  14. ) ? nread : n;
  15. n += nread;
  16. if (n >= len)
  17. return n;
  18. // if not closed but no bytes available, return
  19. InputStream input = in;
  20. )
  21. return n;
  22. }
  23. }

略过多少字节

  1. public synchronized long skip(long n) throws IOException {
  2. getBufIfOpen(); // Check for closed stream
  3. ) {
  4. ;
  5. }
  6. long avail = count - pos;
  7. ) {
  8. // If no mark position set then don't keep in buffer
  9. //从上面的注释可以知道,这也是一个巧妙的方法,如果没有mark标记,
  10. // 则直接从原始输入流中skip
  11. )
  12. return getInIfOpen().skip(n);
  13. // Fill in buffer to save bytes for reset
  14. fill();
  15. avail = count - pos;
  16. )
  17. ;
  18. }
  19. //该方法的实现为尽量原则,不保证一定略过规定的字节数。
  20. long skipped = (avail < n) ? avail : n;
  21. pos += skipped;
  22. return skipped;
  23. }

估计目前可用的字节数,原始流中可用的字节数+缓冲区中可用的字节数

  1. public synchronized int available() throws IOException {
  2. return getInIfOpen().available() + (count - pos);
  3. }

标记位置:

  1. public synchronized void mark(int readlimit) {
  2. marklimit = readlimit;
  3. markpos = pos;
  4. }

重置位置:该实现清晰的表明下一读取位置被推到了以前的标记位置,以实现重新读取区段的功能

  1. public synchronized void reset() throws IOException {
  2. getBufIfOpen(); // Cause exception if closed
  3. )
  4. throw new IOException("Resetting to invalid mark");
  5. pos = markpos;
  6. }

关闭流:首先通过线程安全的方式设置了内部的缓冲区引用为空,然后再对原始输入流进行关闭。

  1. public void close() throws IOException {
  2. byte[] buffer;
  3. while ( (buffer = buf) != null) {
  4. if (bufUpdater.compareAndSet(this, buffer, null)) {
  5. InputStream input = in;
  6. in = null;
  7. if (input != null)
  8. input.close();
  9. return;
  10. }
  11. // Else retry in case a new buf was CASed in fill()
  12. }
  13. }

BufferedInputStream详解的更多相关文章

  1. java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET

    java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET 亲,“社区之星”已经一周岁了!      社区福利快来领取免费参加MDCC大会机会哦    Tag功能介绍—我们 ...

  2. Java反射机制详解

    Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反 ...

  3. URLConnection类详解-转

    转-http://www.cnblogs.com/shijiaqi1066/p/3753224.html 1. URLConnection概述 URLConnection是一个抽象类,表示指向URL指 ...

  4. Java IO 详解

    Java IO 详解 初学java,一直搞不懂java里面的io关系,在网上找了很多大多都是给个结构图草草描述也看的不是很懂.而且没有结合到java7 的最新技术,所以自己来整理一下,有错的话请指正, ...

  5. 基于JavaSE阶段的IO流详解

    1.IO流基本概述 在Java语言中定义了许多针对不同的传输方式,最基本的就是输入输出流(俗称IO流),IO流是属于java.io包下的内容,在JavaSE阶段主要学下图所示的: 其中从图中可知,所有 ...

  6. java基础之JDBC一:概述及步骤详解

    1. JDBC的简介 概述: 就是Java用来操作不同数据库(DBMS)的类库(技术), 本质就是一些类和接口. /* 类: DriverManager 接口: Driver, Connection, ...

  7. Android 多线程之IntentService 完全详解

    关联文章: Android 多线程之HandlerThread 完全详解 Android 多线程之IntentService 完全详解 android多线程-AsyncTask之工作原理深入解析(上) ...

  8. Android 多线程之HandlerThread 完全详解

    关联文章: Android 多线程之HandlerThread 完全详解 Android 多线程之IntentService 完全详解 android多线程-AsyncTask之工作原理深入解析(上) ...

  9. Properties类使用详解

    Java Properties类使用详解   概述 Properties 继承于 Hashtable.表示一个持久的属性集,属性列表以key-value的形式存在,key和value都是字符串. Pr ...

随机推荐

  1. 冒泡排序(C++)

    冒泡排序(C++) 冒泡排序(C++): 原理是临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换, 这样一趟过去后,最大或最小的数字被交换到了最后一位, 然后再从头开始进行两两比较交换, ...

  2. 【清橙A1094】【牛顿迭代法】牛顿迭代法求方程的根

    问题描述 给定三次函数f(x)=ax3+bx2+cx+d的4个系数a,b,c,d,以及一个数z,请用牛顿迭代法求出函数f(x)=0在z附近的根,并给出迭代所需要次数. 牛顿迭代法的原理如下(参考下图) ...

  3. 前端开发web组件之旅(一)-- 定义与加载组件

    /* 前言 */ 自上而下的 职责和API应用层框架层框架浏览器 一 组件定义与调用 1.增加一个组件 tabview.css ------------------------------------ ...

  4. nginx服务器,php-fpm重启

    1.重启nginx服务器:首先whereis nginx找到你的nginx命令执行文件所在目录,直接/usr/local/nginx/sbin/nginx -s reload 这个路径可能每个人不一样 ...

  5. [Linux]XAMPP安装

    XAMPP安装下载地址:http://xiazai.zol.com.cn/index.php?c=Detail_DetailMini&n=19e18f86d0596b5cd&softi ...

  6. decimall类型数据

    同样是decimal(18,5)   和 decimal(18,4)  在VB中经过几次转化过后,数据就有可能改变. 遇到的情况 decimal(18,5)到  decimal(18,4)转换过程中数 ...

  7. tomcat出现的PermGen Space问题(bat,或者eclipse启动。)

    参考地址 http://www.blogjava.net/allen-zhe/archive/2007/12/18/168556.html 参考地址:http://javavsxiaoming.ite ...

  8. sqlserver 学习

    http://www.cnblogs.com/CareySon/category/411344.html SQL Server查找的最小单位实际上是页.也就是说即使你只查找一行很小的数据,SQL Se ...

  9. BZOJ 1831 逆序对

    Description 小可可和小卡卡想到Y岛上旅游,但是他们不知道Y岛有多远.好在,他们找到一本古老的书,上面是这样说的: 下面是N个正整数,每个都在\(1 \sim K\)之间.如果有两个数\(A ...

  10. Matlab读取cifar10 train_quick.sh输出txt中信息

    感谢 网友 Vagrant的提醒.之前 一直就看个最后的accuracy.这个应该并不靠谱.最好把说有的信息都看一下.而一个一个看.根本记不住.只能把数据读取在图片中显示一下,才比较直观. 本文就是读 ...