原文地址:http://blog.sina.com.cn/s/blog_67f995260101huxz.html
BufferedInputStream是一个带有缓冲区的输入流,通常使用它可以提高我们的读取效率,现在我们看下BufferedInputStream的实现原理:

BufferedInputStream内部有一个缓冲区,默认大小为8M,每次调用read方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓
冲区无可读数据),则选择从物理数据源(譬如文件)读取新数据(这里会尝试尽可能读取多的字节)放入到缓冲区中,最后再将缓冲区中的内容部分或全部返回给
用户.由于从缓冲区里读取数据远比直接从物理数据源(譬如文件)读取速度快,所以BufferedInputStream的效率很高!

在具体看源码之前,我们还需要了解BufferedInputStream的mark操作:void
mark(int markLimit)

当你调用mark方法时,内部会保存一个markPos标志,它的值为目前读取字节流的pos位置,倘若你调用reset方法,这时候会把pos重置为
markPos的值,这样你就可以重读已经读过的字节.好像说的不是很清楚,那我们打个比方:有一段字节流是abcdefg,
当你读取完字母a调用mark方法(此时markPos指向字母b),接着你继续读取字母b,字母c,字母d,然后此时你调用reset方法(内部把
pos重置为markPos),当你再读取下一个字节的时候,你会发现你读取到的是b而不是字母e,这样通过mark方法我们就是实现了重复读(re-
read
the same bytes)

mark方法中还有个参数markLimit,它的值表示在调用mark方法后reset方法前最多允许读取的字节数(根据我的测试,以及查看源代码发
现,这个最大字节数,其实是由markLimit和buffer.size中较大的那个决定的),如果超过这个限制,则在调用reset方法时会
报:Reseting
to invalid mark 异常

说完了这么多,让我们来赶紧看看源码吧:

  1. // BufferedInputStream主要有这两个构造方法
  2. public BufferedInputStream(InputStream in) {
  3. this(in, defaultBufferSize);   // 默认缓冲区为8M
  4. }
  5. public BufferedInputStream(InputStream in, int size) {
  6. super(in);
  7. if (size <= 0) {
  8. throw new IllegalArgumentException("Buffer size <= 0");
  9. }
  10. buf = new byte[size];
  11. }
你需要指定InputStream(装饰模式的体现)以及bufferSize(可选)

当我们调用read()方法时,它在内部做了一下事情:

  1. public synchronized int read() throws IOException {
  2. if (pos >= count) {         // 检查是否有可读缓冲数据
  3. fill();                 // 没有缓冲数据可读,则从物理数据源读取数据并填充缓冲区
  4. if (pos >= count)       // 若物理数据源也没有多于可读数据,则返回-1,标示EOF
  5. return -1;
  6. }
  7. // 从缓冲区读取buffer[pos]并返回(由于这里读取的是一个字节,而返回的是整型,所以需要把高位置0)
  8. return getBufIfOpen()[pos++] & 0xff;
  9. }
  10. private byte[] getBufIfOpen() throws IOException {
  11. byte[] buffer = buf;      // buf为内部缓冲区
  12. if (buffer == null)
  13. throw new IOException("Stream closed");
  14. return buffer;
  15. }

其中pos为缓冲区buffer下一个可读的数组下标,我们可以一直从缓冲区里读取数据,直到pos变为count(此时只能从物理数据源读取数据),下面我们就分析下,当缓冲区里没有数据可读时,BufferInputStream是如何处理的:

1.若用户没有开启re-read功能(即未调用mark方法),当pos ==
count时,我们只需要将pos重新置为0,然后从物理源读取数据(假设读到了n个字节),最后把count设置成 n + pos 即可
(其实就是n,因为pos之前被设置成了0),
当下次你在调用read方法时,就直接从缓冲读取,非常快速(如下图);

2.若用户调用了mark方法,情况就变得很复杂了,为什么呢?
这意味着我们需要保存从markPos到pos这段数据(以供用户re-read),现在我们分情况讨论:

a.此时pos <
buffer.length,这意味着缓冲区还有多余空间,所以我们可以继续从物理数据源读取数据放入到缓冲区中(如下图);

b.pos ==
buffer.length,这意味着缓冲区已经没有多余空间,所以只能清空缓冲区内容,但是不要忘了,我们还必须保留原来

markPos到pos那段数据,以供用户re-read,所以我需要这样做:

  1. // 计算需要保留多少字节的数据
  2. int sz = pos - markPos;
  3. // 然后拷贝到缓冲头部
  4. System.arraycopy(buffer, markpos, buffer, 0, sz);
  5. // 重置pos以及markPos
  6. pos=sz;
  7. markPos=0;
接着从缓冲区的sz 到
buffer.length又变成可用区间,用来存放从物理数据源读到的数据(如下图)

到这里似乎问题完美的解决了,其实不然,我们忘记考虑markPos失效问题,以及若pos - markPos ==
buffer.length,那么移了等于白移动,还是没有挪出多余空间,所以实我们应该这样做(后面讨论都是建立在pos ==
buffer.length的基础上):

2.1 若markPos > 0, 那么 pos -
makrPos一定小于缓冲区大小,这样意味着我们按照刚才的算法挪动后,缓冲区就有了空余空间

2.2 若makrPos == 0, 
这意味着需要保存的数据满满的充斥着缓冲区,所以这时候我们是无法通过挪动位置来使缓冲区有多余空间的,所以我们只可以清空或扩展缓冲区

2.2.1 当buffer.length >= marklimit时
,此时意味着markPos已经失效,用户不可以在进行re-read,所以此时我们就可以简单释放整个缓冲区了:pos=0,
markPos=-1;

2.2.2
其余情况,意味着markPos还有效,所以我们只能通过扩展缓冲区的方式来使缓冲区有多余空间

说了这么多,我们还是看下相关代码吧:

  1. private void fill() throws IOException {
  2. byte[] buffer = getBufIfOpen();        // 得到当前缓冲区
  3. if (markpos < 0)                          // 对应情况1
  4. pos = 0;
  5. else if (pos >= buffer.length)            // 对应情况2
  6. if (markpos > 0) {                    // 对应情况2.1
  7. int sz = pos - markpos;
  8. System.arraycopy(buffer, markpos, buffer, 0, sz);
  9. pos = sz;
  10. markpos = 0;
  11. } else if (buffer.length >= marklimit) {  // 对应情况2.2.1
  12. markpos = -1;
  13. pos = 0;
  14. } else {                                  // 对应情况2.2.2
  15. int nsz = pos * 2;
  16. if (nsz > marklimit)
  17. nsz = marklimit;
  18. byte nbuf[] = new byte[nsz];
  19. System.arraycopy(buffer, 0, nbuf, 0, pos);
  20. ...
  21. }
  22. count = pos;
  23. int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
  24. if (n > 0)
  25. count = n + pos;
  26. }

好了关于BufferedInputStream就说道这里,它的 read(byte b[], int
off, int
len)其实内部实现也大概如此:先从缓冲区读,如果读不到则从物理数据源读取并刷新到缓冲区(可能需要对缓冲区原来内容作必要的挪动或者对缓冲区大小进行扩展)

BufferedInputStream实现原理分析的更多相关文章

  1. 【Java编程实战】Metasploit_Java后门运行原理分析以及实现源码级免杀与JRE精简化

    QQ:3496925334 文章作者:MG1937 CNBLOG博客ID:ALDYS4 未经许可,禁止转载 某日午睡,迷迷糊糊梦到Metasploit里有个Java平台的远控载荷,梦醒后,打开虚拟机, ...

  2. Handler系列之原理分析

    上一节我们讲解了Handler的基本使用方法,也是平时大家用到的最多的使用方式.那么本节让我们来学习一下Handler的工作原理吧!!! 我们知道Android中我们只能在ui线程(主线程)更新ui信 ...

  3. Java NIO使用及原理分析(1-4)(转)

    转载的原文章也找不到!从以下博客中找到http://blog.csdn.net/wuxianglong/article/details/6604817 转载自:李会军•宁静致远 最近由于工作关系要做一 ...

  4. 原子类java.util.concurrent.atomic.*原理分析

    原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...

  5. Android中Input型输入设备驱动原理分析(一)

    转自:http://blog.csdn.net/eilianlau/article/details/6969361 话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反 ...

  6. 转载:AbstractQueuedSynchronizer的介绍和原理分析

    简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过 ...

  7. Camel运行原理分析

    Camel运行原理分析 以一个简单的例子说明一下camel的运行原理,例子本身很简单,目的就是将一个目录下的文件搬运到另一个文件夹,处理器只是将文件(限于文本文件)的内容打印到控制台,首先代码如下: ...

  8. NOR Flash擦写和原理分析

    NOR Flash擦写和原理分析 1. NOR FLASH 的简单介绍 NOR FLASH 是很常见的一种存储芯片,数据掉电不会丢失.NOR FLASH支持Execute On Chip,即程序可以直 ...

  9. 使用AsyncTask异步更新UI界面及原理分析

    概述: AsyncTask是在Android SDK 1.5之后推出的一个方便编写后台线程与UI线程交互的辅助类.AsyncTask的内部实现是一个线程池,所有提交的异步任务都会在这个线程池中的工作线 ...

随机推荐

  1. Android手机SSH Client客户端推荐JuiceSSH

    Windows上建立ssh服务器 参见: http://www.cnblogs.com/xred/archive/2012/04/21/2461627.html Android手机SSH Client ...

  2. STL string常用操作指令

    s.insert(pos,args); 在pos之前插入args指定的字符.pos可以是一个下标或一个迭代器.接受下标的版本返回一个指向s的引用;接受迭代器的版本返回指向第一个插入字符的迭代器. s. ...

  3. codeforces Ilya and Matrix

    http://codeforces.com/contest/313/problem/C #include <cstdio> #include <cstring> #includ ...

  4. 简单工厂模式 - OK

    简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例.简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现. 使用场景: 工厂类负责创建的对象比较少: 客户只知道传 ...

  5. C#进程间通信--API传递参数(SendMessage)

    原文 C#进程间通信--API传递参数(SendMessage)  我们不仅可以传递系统已经定义好的消息,还可以传递自定义的消息(只需要发送消息端和接收消息端对自定义的消息值统一即可).下面的发送和接 ...

  6. CH Round #53 -密室

    描述 有N个密室,3种钥匙(红色,绿色,白色)和2种锁(红色,绿色),红色钥匙只能开红色的锁,绿色钥匙只能开绿色的锁,白色钥匙可以开红色的锁和绿 色的锁,一把钥匙使用一次之后会被扔掉.每个密室由一扇门 ...

  7. 【转】iOS 解决ipv6问题

    解决ipv6的方法有很多种,由于现在国内的网络运营商还在使用ipv4的网络环境,所以appstore应用不可能大范围去修改自己的服务器, 而且国内的云服务器几乎没有ipv6地址. 这里附上苹果开发平台 ...

  8. Linux查看系统状态及备份

    1. 如何看当前Linux系统有几颗物理CPU和每颗CPU的核数?cat /proc/cpuinfo将CPU的总核数除以物理CPU的个数,得到每颗CPU的核数.2. 查看系统负载有两个常用的命令,是哪 ...

  9. 如何判断Linux load的值是否过高

    1.先使用top看下CPU占用高的进程,找出进程的进程ID(pid): 查看方法:top 2.根据进程ID(pid)查看是进程的那些线程占用CPU高. 查看方法:top -Hp pid 3.使用pst ...

  10. Asp.Net中JSON的序列化和反序列化-----JavaScriptSerializer ,加上自己工作心得

    在工作中和手机通信用到web服务和javascriptSerializer,返回json数据,供手机端调用,一开始返回的数据是一大堆,比如 [{"word_picture9":&q ...