【本文转载于http://icanfly.iteye.com/blog/1207397】

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. 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. protected int markpos = -1;/*表示标记位置,该标记位置的作用为:实现流的标记特性,即流的某个位置可以被设置为标记,允许通过设置reset(),将流的读取位置进行重置到该标记位置,但是InputStream注释上明确表示,该流不会无限的保证标记长度可以无限延长,即markpos=15,pos=139734,该保留区间可能已经超过了保留的极限(如下)*/
  7. 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 <= 0) 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方法实现贪婪读取。

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. int n = 0;
  11. for (;;) {
  12. int nread = read1(b, off + n, len - n);
  13. if (nread <= 0)
  14. return (n == 0) ? 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. if (input != null && input.available() <= 0)
  21. return n;
  22. }
  23. }

略过多少字节

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

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

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

BufferedInputStream学习笔记相关推荐

  1. java学习笔记16--I/O流和文件

    本文地址:http://www.cnblogs.com/archimedes/p/java-study-note16.html,转载请注明源地址. IO(Input  Output)流 IO流用来处理 ...

  2. java学习笔记十三

    11. 凡是继承了FilterOutputStream或FilterInputStream的类都是过滤流,也就是说他们不能直接跟目标(键盘,文件,网络等,节点流可以)数据打交道,只能包装 Intput ...

  3. 菜鸟学习笔记:Java提升篇6(IO流2——数据类型处理流、打印流、随机流)

    菜鸟学习笔记:Java IO流2--其他流 字节数组输入输出流 数据类型处理流 基本数据类型 引用类型 打印流 System.in.System.out.System.err 随机流RandomAcc ...

  4. 菜鸟学习笔记:Java提升篇5(IO流1——IO流的概念、字节流、字符流、缓冲流、转换流)

    菜鸟学习笔记:Java IO流1--IO流的概念.字节流.字符流.缓冲流.转换流 IO流的原理及概念 节点流 字节流 文件读取 文件写出 文件拷贝 文件夹拷贝 字符流 文件读取 文件写出 处理流 缓冲 ...

  5. Java 基础 第3阶段:高级应用——尚硅谷学习笔记(含面试题) 2023年

    Java 基础 第 3 阶段:高级应用--尚硅谷学习笔记(含面试题) 2023 年 Java 基础 第 3 阶段:高级应用--尚硅谷学习笔记(含面试题) 2023 年 第 9 章 异常处理 9.1 异 ...

  6. 尚学堂JAVA基础学习笔记_2/2

    尚学堂JAVA基础学习笔记_2/2 文章目录 尚学堂JAVA基础学习笔记_2/2 写在前面 第10章 IO技术 1. IO入门 2. IO的API 3. 装饰流 4. IO实战 5. CommonsI ...

  7. Java基础知识学习笔记总结

    Java学习笔记总结 java基础复习 1. 抽象类可以有构造器,可以有一个非抽象的父类 2. 垃圾回收机制回收的是堆里面的内存,栈里面的数据自动入栈自动出栈 3. 引用类型的数据在堆当中,内存中操作 ...

  8. Java学习笔记整理-知识梳理+JDK1.8详细文档

    链接:java开发手册 提取码:kes8 链接:JDK1.8详细文档 提取码:n9zo JavaSE 面向对象编程(Object-oriented programming) 封装 类(class) 类 ...

  9. 《Java学习笔记(第8版)》学习指导

    <Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...

最新文章

  1. 2.7 usb摄像头之usb摄像头描述符打印
  2. vbs劫持快捷键并执行程序
  3. 日志库EasyLogging++学习系列(4)—— 格式说明符
  4. JavaScript学习笔记07【6个经典案例——电灯开关、轮播图、自动跳转首页、动态表格、表格全选、表单验证】
  5. OpenCV--cvScaler颜色赋值
  6. 201671010114 2016-2017-2 《Java程序设计》学习总结
  7. 1011 Sticks
  8. 【优化预测】基于matlab贝叶斯网络优化LSTM预测【含Matlab源码 1329期】
  9. ansys大变形开关要不要打开_ANSYS与ABAQUS比较之实例7橡胶垫圈的受压分析
  10. WhereHows 数据发现和管理工具
  11. 软考中级-软件设计师-查缺补漏
  12. W25Q64简介(译)
  13. java Virtual Machine Launcher
  14. uniapp中页面白屏问题
  15. 写给当初的你,现在的我
  16. 你好法语A1语法单元汇总(unité 7)
  17. dnf如何快速拾取物品_DNF宠物之最,快来Pick出你的挚爱
  18. 【思维进阶】就业市场调研-游戏行业
  19. 项目管理证书PMP的含金量高吗?
  20. Foxmail 登录 qq 账号时无法登录 提示我们设置了独立密码或使用授权码登录的解决方法...

热门文章

  1. python学习day17 递归函数
  2. Linux查看修改时间、时区
  3. 【公共类库】加密解密
  4. 【WIN10】WIN2D——基本圖形的繪製
  5. 浅谈 Vue 项目优化
  6. kubeadm安装k8s 1.13版本
  7. 架构设计杂谈004——架构师
  8. SpringBoot项目遇到的一些问题
  9. python中*args和**args的不同
  10. 看完这篇文章保你面试稳操胜券——基础篇(html/css)