BufferedInputStream

来源:互联网 发布:mirai僵尸网络 编辑:程序博客网 时间:2024/05/16 13:43

BufferedInputStream 要点


1.BufferedInputStream 原理概览图

BufferedInputStream 的缓存并非作为一个独立的 additional 之物实现,而是将读的行为与缓存的行为高度耦合。

一般的缓存器的实现方式为:缓存向上层提供读数据的服务,向下层主动地获得数据(发生在自定义的时机)。这种缓存的实现方式好处是上层不用关心下层怎么取到的数据。


两种缓存的设计方式


2.详解原理

注意,读和更新缓存均是对缓存数组的操作,因此所有的下标均是缓存数组中的下标,而不是整个流中的下标。如 pos 指待读取的下一个字节在缓存数组中的下标,markPos 指被标记的缓存数组中的位置。

    • 一次性读多个数据时,a)先使用完缓存中的数据(哪怕只有一个字节也要使用)。b)之后再去读原始流数据:若还要读的数据大于缓存的长度,则不回写缓存;否则写缓存。(注意,读多个数据如果使用了 b 步骤,则会将 pos 和 count 更新:前者把两者都更新为 0,后者更新为某值。这个行为保证了下一次读数据时是继续往后 ,不会重新读)
    • 一次性读一个字节,无需缀言。

  • 更新缓存(主要是 markPos 的影响)
    • 没有 markPos (没有调用过 mark 方法),直接丢弃掉整个缓存,再更新整个缓存
    • 有 markPos
      • 若 markPos 不为0:自 markpos 移动缓存数组到首位置,再在后面填充满缓存数组。
      • 若 markPos 为 0:
        • marklimit 大于缓存数组的长度,则对缓存数组扩容:新长度取原长度两倍与 marklimit 的较小值。之所以扩大缓存这是因为调用者已经提出了较大的读取需求:marklimit 大于当前缓存大小。
        • 否则使 markPos 失效,这是因为接下来整个缓存都要被更新,markPos 指向 0,则更新后这个位置是没有意义的。之后再调用 reset() 就会抛出 IOException 异常。
  • mark
    • mark 的语义是,在读取过程中标记一个点(mark),到后面可以再回退到这个点 (reset),再读取。mark 有一个参数 marklimit,它的语义是,自标记点(包括在内)往后多少个字节可以再次读取。这个语义对于BufferedInputStream 来说特别有意义,因为缓存区不可能无穷大,有可能标记点的数据已经不在缓存中,因此,必须要对 mark 作限制。
    • BufferedInputStream 中对于 mark 的实现比较复杂,这是因为有缓存导致的,缓存相当于是数据和读操作间的一个中间层,mark 通过调用者与中间层进行状态保持,与缓存写和读相互影响。具体的影响已经在上面一点中阐述过。
    • 需要注意的有以下几点:
      • reset 可能会失败:mark 的位置已经不在缓存中了。
      • 标记之后发生过多少次读操作无关紧要,只要 mark 的位置的数据还在缓存中,就可以通过 reset 到标记位置,可从此处往后最多读到缓存末尾,并非只能读 marklimit 个字节。
      • marklimit 最好根据实际情况来定,不可过大,因为这会导致缓存不可逆向的扩大,加大内存压力。把这个缺点当优点来看,可以将 marklimit 设置为整个流的长度以此让整个流都缓存到内存中,加快随机访问的速度。

  • skip
    • skip 的语义是向后跳过 n 个字节后再往后读。注意,此处参数 n 若小于 0 则被视为 0;不同于 FileInputStream,可以接收 n < 0 表示向前跳过。
    • 需要注意的是,skip(n) 方法不一定真的跳过了 n ,在缓存中有数据时,它最多跳过缓存数组有效的数据长度!而缓存为空时,可以最多跳到流的末尾。

这就存在一个非常有意思的现象:skip(m);skip(n);这两句的效果不等价于 skip(m+n);

设缓存数组长度为 8.

MyBufferedInputsteam bis = new MyBufferedInputsteam(new FileInputStream(new File("d:/test.txt")),8);
bis.read();
long skiped = bis.skip(7);//此句执行完毕后,缓存有效数据长度为 7
System.err.println("skip(7) : " + skiped);//skiped = 7;执行完毕后缓存长度为0

skiped = bis.skip(10);
System.err.println("skip(10) : " + skiped);//skiped=10;缓存没有数据,则直接调用底层的 skip,可以跳过任意长度直到流末尾

上面的代码说明,调用 read()读掉一个字节后,skip(7);skip(10);这两句的效果是总共 skip 了 17 个长度。

而 skip(17) 因为缓存数据只有 7 字节,所以最多只能 skip 7 字节。因此有这个结论:skip(m) + skip(n) >= skip(m+n)

所以写代码时,如果代码逻辑是先要跳过 m 字节,再要跳过 n 字节就一定要调用多次 skip,不要偷懒调用一次。反之,如果需要一次调用 skip 多个字节,就不要分多次调用了。

0 0
原创粉丝点击