zoukankan      html  css  js  c++  java
  • BufferedInputStream详解

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

    InputStream 
    |__FilterInputStream 
            |__BufferedInputStream
     

    首先了解一下FilterInputStream: 
    FilterInputStream通过装饰器模式将InputStream封装至内部的一个成员变量: 
    Java代码  收藏代码
    1. protected volatile InputStream in;  

    需要注意的是该成员变量使用了volatile关键字进行修饰,这意味着该成员变量的引用的内存可见性为多线程即时可见的。 
    其它地方FilterInputStream将所有的操作委托给了in这个成员进行操作。 

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



    通过构造函数可以看到:初始化了一个byte数组作为缓冲区域 
    Java代码  收藏代码
    1. public BufferedInputStream(InputStream in, int size) {  
    2.     super(in);  
    3.         if (size <= 0) {  
    4.             throw new IllegalArgumentException("Buffer size <= 0");  
    5.         }  
    6.     buf = new byte[size];  
    7. }  



    这个类中最为重要的方法是fill()方法,它提供了缓冲区域的读取、写入、区域元素的移动更新等。下面着重分析一下该方法: 
    Java代码  收藏代码
    1. private void fill() throws IOException {  
    2.         byte[] buffer = getBufIfOpen();  
    3.     if (markpos < 0) {  
    4.           /*如果不存在标记位置(即没有需要进行reset的位置需求) 
    5.             则可以进行大胆地直接重置pos标识下一可读取位置,但是这样 
    6.             不是会读取到以前的旧数据吗?不用担心,在后面的代码里☆会实现输入流的新  
    7.             数据填充*/  
    8.         pos = 0;          
    9.     }else if (pos >= buffer.length){  
    10.        /* 位置大于缓冲区长度,这里表示已经没有可用空间了 */  
    11.         if (markpos > 0) {     
    12.              /* 表示存在mark位置,则要对mark位置到pos位置的数据予以保留, 
    13.                 以确保后面如果调用reset()重新从mark位置读取会取得成功*/  
    14.         int sz = pos - markpos;  
    15.                 /*该实现是通过将缓冲区域中markpos至pos部分的移至缓冲区头部实现*/  
    16.         System.arraycopy(buffer, markpos, buffer, 0, sz);  
    17.         pos = sz;  
    18.         markpos = 0;  
    19.         } else if (buffer.length >= marklimit) {  
    20.                 /* 如果缓冲区已经足够大,可以容纳marklimit,则直接重置*/  
    21.                 markpos = -1;     
    22.         pos = 0;/* 丢弃所有的缓冲区内容 */  
    23.         } else {          
    24.                 /* 如果缓冲区还能增长的空间,则进行缓冲区扩容*/  
    25.         int nsz = pos * 2;  
    26.                 /*新的缓冲区大小设置成满足最大标记极限即可*/  
    27.         if (nsz > marklimit)  
    28.             nsz = marklimit;  
    29.         byte nbuf[] = new byte[nsz];  
    30.                 //将原来的较小的缓冲内容COPY至增容的新缓冲区中  
    31.         System.arraycopy(buffer, 0, 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.         if (n > 0)  
    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表示). 
    Java代码  收藏代码
    1. public synchronized int read() throws IOException {  
    2.     if (pos >= count) {  
    3.            /*表示读取位置已经超过了缓冲区可用范围,则对缓冲区进行重新填充*/  
    4.         fill();  
    5.            /*当填充后再次读取时发现没有数据可读,证明读到了流末尾*/  
    6.         if (pos >= count)  
    7.         return -1;  
    8.     }  
    9.         /*这里表示读取位置尚未超过缓冲区有效范围,直接返回缓冲区内容*/  
    10.     return getBufIfOpen()[pos++] & 0xff;  
    11. }  


    一次读取多个字节(尽量读,非贪婪) 
    Java代码  收藏代码
    1. private int read1(byte[] b, int off, int len) throws IOException {  
    2.     int avail = count - pos;  
    3.     if (avail <= 0) {  
    4.         /*这里使用了一个巧妙的机制,如果读取的长度大于缓冲区的长度 
    5.               并且没有markpos,则直接从原始输入流中进行读取,从而避免无谓的 
    6.               COPY(从原始输入流至缓冲区,读取缓冲区全部数据,清空缓冲区, 
    7.               重新填入原始输入流数据)*/  
    8.         if (len >= getBufIfOpen().length && markpos < 0) {  
    9.         return getInIfOpen().read(b, off, len);  
    10.         }  
    11.             /*当无数据可读时,从原始流中载入数据到缓冲区中*/  
    12.         fill();  
    13.         avail = count - pos;  
    14.         if (avail <= 0return -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方法实现贪婪读取。 
    Java代码  收藏代码
    1. public synchronized int read(byte b[], int off, int len)  
    2.     throws IOException  
    3.     {  
    4.         getBufIfOpen(); // Check for closed stream  
    5.         if ((off | len | (off + len) | (b.length - (off + len))) < 0) {  
    6.         throw new IndexOutOfBoundsException();  
    7.     } else if (len == 0) {  
    8.             return 0;  
    9.         }  
    10.   
    11.     int n = 0;  
    12.         for (;;) {  
    13.             int nread = read1(b, off + n, len - n);  
    14.             if (nread <= 0)   
    15.                 return (n == 0) ? nread : n;  
    16.             n += nread;  
    17.             if (n >= len)  
    18.                 return n;  
    19.             // if not closed but no bytes available, return  
    20.             InputStream input = in;  
    21.             if (input != null && input.available() <= 0)  
    22.                 return n;  
    23.         }  
    24.     }  

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

    估计目前可用的字节数,原始流中可用的字节数+缓冲区中可用的字节数 
    Java代码  收藏代码
    1. public synchronized int available() throws IOException {  
    2.     return getInIfOpen().available() + (count - pos);  
    3.     }  

    标记位置: 
    Java代码  收藏代码
    1. public synchronized void mark(int readlimit) {  
    2.     marklimit = readlimit;  
    3.     markpos = pos;  
    4.     }  

    重置位置:该实现清晰的表明下一读取位置被推到了以前的标记位置,以实现重新读取区段的功能 
    Java代码  收藏代码
    1. public synchronized void reset() throws IOException {  
    2.         getBufIfOpen(); // Cause exception if closed  
    3.     if (markpos < 0)  
    4.         throw new IOException("Resetting to invalid mark");  
    5.     pos = markpos;  
    6.     }  


    关闭流:首先通过线程安全的方式设置了内部的缓冲区引用为空,然后再对原始输入流进行关闭。 
    Java代码  收藏代码
    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.     }  
  • 相关阅读:
    微信 token ticket jsapi_ticket access_token 获取 getAccessToken get_jsapi_ticket方法
    PHP 日志 记录 函数 支持 数组 对象 新浪 sae 环境 去掉 空格 换行 格式化 输出 数组转字符串
    原生 原始 PHP连接MySQL 代码 参考mysqli pdo
    PHP 数字金额转换成中文大写金额的函数 数字转中文
    使用PHPMailer发送带附件并支持HTML内容的邮件
    设置输出编码格式 header 重定向 执行时间 set_time_limit 错误 报告 级别 error_reporting
    html5 bootstrap pannel table 协议 公告 声明 文书 模板
    指向指针的指针
    二级指针
    c语言:当指针成为参数后
  • 原文地址:https://www.cnblogs.com/marcotan/p/4256945.html
Copyright © 2011-2022 走看看