个人博客传送门

一、mark / reset 的作用

Android Glide 3.7.0 源码解析(七) , 细说图形变换和解码有提到过RecyclableBufferedInputStream 对于 mark(int marklimit)reset() 方法的作用, 本文则是探讨具体的实现思路


mark(int marklimit) 的作用是在流中创建一段起点是 markPos 长度是 markLimit 的可被重复读取区域, 当调用 reset() 方法时流的读取位置会回到 markPos , 实现 markLimit 可以被重复读取

注意:

  • 当读取位置到达 readPos_2 时, reset() 方法会失效, 因为已经超出 markLimit 的长度范围了
  • mark(int marklimit) 标记的起始点 markPos 为方法调用时流的读取位置( 上图中 mark 时, 读取位置是 readPos_0, 所以 markPos == readPos_0 )

我们都知道 Stream 流只能被读取一遍

二、实现思路

上文中的图解还只是黑盒运行路线查看, 下面来分析具体实现的图解

由于 InputStream 只能被读取一遍就消耗了, 所以我们要做到重复读取必须借助一个外部工具, 此处用的是 buf 一个初始大小为 64K 的 byte[] 数组, 下面来对上图元素进行说明

  • 右侧是还存在 InputStream 中的未被读取的数据
  • 左侧是已经从流程读取出来的数据, 存在 buf
  • count 代表的是 buf 中有效数据的数量 ( 你读到流的末尾了,可能会产生 buf 读不满的情况 )
  • markpos 代表 mark() 标记的位置
  • 蓝色条状 则是需要支持被重复读取的部分了, marklimit 表示它的长度
  • pos 代表当前正在读取的位置

现在我们有了 buf 作为缓存, 可以将 mark 的流数据缓存在 buf 中, 这样就可以重复读取了

注意: buf 大小不是恒定不变的, 当 marklimit > buf.length 并满足一些其他条件时, buf 会被2倍扩容

三、源码分析

看看 RecyclableBufferedInputStream 都有哪些变量( 其实在介绍思路的时候都讲过了 )

public class RecyclableBufferedInputStream extends FilterInputStream {// buf 缓存从流中读取出来的数据private volatile byte[] buf;// buf 有效数据的长度private int count;// mark 标记数据的长度private int marklimit;// mark 标记的位置, 默认 -1 表示没有标记private int markpos = -1;// 当前读取 buf 的位置private int pos;}

mark() 和 reset()

再来看看 mark 和 reset 方法

 public synchronized void mark(int readlimit) {marklimit = Math.max(marklimit, readlimit);markpos = pos;}public synchronized void reset() throws IOException {if (buf == null) {throw new IOException("Stream is closed");}if (-1 == markpos) {throw new InvalidMarkException("Mark has been invalidated");}pos = markpos;}

mark 就做了两件事 赋值 + 赋值

  • marklimit 和旧的作比较取一个最大值
  • markpos 设置为当前读取位置

reset 就做了一件事

  • 当前位置 pos 被重置成 markpos

read()

来看看 read 方法干了啥

 public synchronized int read() throws IOException {// Use local refs since buf and in may be invalidated by an// unsynchronized close()byte[] localBuf = buf;InputStream localIn = in;if (localBuf == null || localIn == null) {throw streamClosed();}// Are there buffered bytes available?if (pos >= count && fillbuf(localIn, localBuf) == -1) {// no, fill bufferreturn -1;}// localBuf may have been invalidated by fillbufif (localBuf != buf) {localBuf = buf;if (localBuf == null) {throw streamClosed();}}// Did filling the buffer fail with -1 (EOF)?if (count - pos > 0) {return localBuf[pos++] & 0xFF;}return -1;}

这个 read() 方法是读取单个字符用的, 每次只读去 1 个byte 返回

  • pos >= count 代表 buf 被读完了, 调用 fillbuf(localIn, localBuf) 去重新向 buf 填充数据
  • count - pos > 0 代表 buf 数据还够, 直接读取返回 localBuf[pos++] & 0xFF

这里 return 为毛是个 int ??? 说好的 byte 呢
因为返回的流数据里面是无符号的 byte 0~255 范围 , 而 java 里面的 byte 默认带符号, - 128 ~ 127, 数据范围不够, 只能用 int 来凑了, 注意这行代码: localBuf[pos++] & 0xFF , 把高 24 位全部清零了, 达到返回 0~255 效果

这中间好像没有提到 mark 的部分, 猜测只能是在 fillbuf 函数中实现了, 跟进去看看

fillbuf()

代码较长, 耐心看看

 private int fillbuf(InputStream localIn, byte[] localBuf)throws IOException {// 没有标记, 或者读取区域超过了标记的范围, 直接从流中读取数据到 bufif (markpos == -1 || pos - markpos >= marklimit) {// Mark position not set or exceeded readlimitint result = localIn.read(localBuf);if (result > 0) {markpos = -1;pos = 0;count = result;}return result;}// [标记有效] 这里 buf.length 不够了, 扩容 2 倍if (markpos == 0 && marklimit > localBuf.length && count == localBuf.length) {// Increase buffer size to accommodate the readlimitint newLength = localBuf.length * 2;if (newLength > marklimit) {newLength = marklimit;}if (Log.isLoggable(TAG, Log.DEBUG)) {Log.d(TAG, "allocate buffer of length: " + newLength);}byte[] newbuf = new byte[newLength];System.arraycopy(localBuf, 0, newbuf, 0, localBuf.length);localBuf = buf = newbuf;} // [标记有效] 标记位置 >0 buf中 标记位置之前还有一批数据可以被舍弃else if (markpos > 0) {System.arraycopy(localBuf, markpos, localBuf, 0, localBuf.length- markpos);}// 重置读取位置和 buf 中有效数据的长度pos -= markpos;count = markpos = 0;// 如果 buf 不满(还有脏数据) 就开始从流中读取填充int bytesread = localIn.read(localBuf, pos, localBuf.length - pos);count = bytesread <= 0 ? pos : pos + bytesread;return bytesread;}

以上一共有三种情况

  • 第一种: 没有 mark 参与或者mark已失效 (markpos == -1 || pos - markpos >= marklimit), 就是普通的从流中读取数据, 填满 buf , 然后把 pos 当前读取位置重置一下 ( 下图绿色表示扩容部分 )

  • 第二种, (markpos == 0 && marklimit > localBuf.length && count == localBuf.length)
    mark 有效, 且 markpos 左侧已经退不可退, 抵到 buf 的最左侧了;
    并且 marklimit 超过了 buf 的长度;
    count == localBuf.length 才进行 2 倍扩容

    • count == localBuf.length 这个条件, 他表示 buf 里面的有效数据必须是满的才进行扩容, buf 只有在读到文件末尾时才可能不满, 当流都读完了, 没数据了, 即使 marklimit 超过 buf 的长度也没必要扩容了, 根本读不到那么多了;
    • 看这几行代码
    int newLength = localBuf.length * 2;
    if (newLength > marklimit) {newLength = marklimit;
    }
    

    如果扩充 2 倍后大于 marklimit 则以 marklimit为准, 否则, 就只扩充 2 倍, 不会一下子扩充到 marklimit 的大小, 对内存的使用可谓是抠搜到极致 ( 可以理解为懒申请, 用到了才去申请更大的 buf )

第二种情况 扩容一: marklimit < buf.length * 2 的情况

2 倍超出 marklimit 的长度, 直接以 marklimit的长度为准, 上图绿色为扩容部分

第二种情况 扩容二: marklimit >= buf.length * 2 的情况

  • 第三种情况 markpos != 0 并且不满足扩容条件

这种情况下, 只需要把 markpos 左边的数据清除, 然后再往 buf 右边空出的位置写入数据

至此, fillbuf() 分析完毕, 一种分三种情况来填充 buf 上面都已做出图示说明

read(byte[] buffer, int offset, int byteCount)

函数很长, 但是逻辑相对简单

public synchronized int read(byte[] buffer, int offset, int byteCount) throws IOException {// 一系列错误检查就不看了, 非主线byte[] localBuf = buf;if (localBuf == null) {throw streamClosed();}if (byteCount == 0) {return 0;}InputStream localIn = in;if (localIn == null) {throw streamClosed();}// 先直接读取 buf int required;if (pos < count) {// There are bytes available in the buffer.int copylength = count - pos >= byteCount ? byteCount : count - pos;System.arraycopy(localBuf, pos, buffer, offset, copylength);pos += copylength;if (copylength == byteCount || localIn.available() == 0) {// buf 里面就够读, 直接返回return copylength;}offset += copylength;// 不够读计算还需要多少required = byteCount - copylength;} else {// buf 已经不能读了, 计算还需要多少required = byteCount;}// 开启循环读到够为止while (true) {int read;// 如果没有 mark 的影响, 直接从流本身读取if (markpos == -1 && required >= localBuf.length) {read = localIn.read(buffer, offset, required);if (read == -1) {// 流读尽了直接返回return required == byteCount ? -1 : byteCount - required;}} else {// 开始利用 fillbuf() 填充 buf 数组if (fillbuf(localIn, localBuf) == -1) {return required == byteCount ? -1 : byteCount - required;}// localBuf may have been invalidated by fillbufif (localBuf != buf) {localBuf = buf;if (localBuf == null) {throw streamClosed();}}// 填充完开始读read = count - pos >= required ? required : count - pos;System.arraycopy(localBuf, pos, buffer, offset, read);pos += read;}required -= read;// 这里判断够不够数, 够的话直接返回if (required == 0) {return byteCount;}// 这里判断流有没有读尽, 读尽了直接返回已读的sizeif (localIn.available() == 0) {return byteCount - required;}offset += read;}}

逻辑其实很简单, 就是先读当前 buf 的内容, 不够的话进行下一步, 开启循环, 如果没有 mark , 则直接从流中读取, 否则, fillbuf 填充 buf 之后 从 buf 再读取一遍, 直至流被读尽或者读够想要的数字跳出循环返回

Android Glide 3.7.0 源码解析(八) , RecyclableBufferedInputStream 的 mark/reset 实现相关推荐

  1. Android多线程之ArrayBlockingQueue源码解析

    阻塞队列系列 Android多线程之LinkedBlockingQueue源码解析 Android多线程之SynchronousQueue源码解析 Andorid多线程之DelayQueue源码分析 ...

  2. solrlucene3.6.0源码解析(三)

    solr索引操作(包括新增 更新 删除 提交 合并等)相关UML图如下 从上面的类图我们可以发现,其中体现了工厂方法模式及责任链模式的运用 UpdateRequestProcessor相当于责任链模式 ...

  3. Heritrix 3.1.0 源码解析(八)

    本文接着分析存储CrawlURI curi的队列容器,最重要的是BdbWorkQueue类及BdbMultipleWorkQueues类 BdbWorkQueue类继承自抽象类WorkQueue,抽象 ...

  4. Heritrix 3.1.0 源码解析(六)

    本文分析BdbFrontier对象的相关状态和方法 BdbFrontier类继承自WorkQueueFrontier类   WorkQueueFrontier类继承自AbstractFrontier类 ...

  5. Heritrix 3.1.0 源码解析(十一)

    上文分析了Heritrix3.1.0系统是怎么添加CrawlURI curi对象的,那么在系统初始化的时候,是怎么载入CrawlURI curi种子的呢? 我们回顾前面的文章,在我们执行采集任务的la ...

  6. Android多线程之IntentService源码解析

    想要了解 IntentService 的工作原理需要先对 Android 系统中以 Handler.Looper.MessageQueue 组成的异步消息处理机制以及 HandlerThread 有所 ...

  7. Heritrix 3.1.0 源码解析(三十四)

    本文主要分析FetchFTP处理器,该处理器用于ftp文件的下载,该处理器的实现是通过封装commons-net-2.0.jar组件来实现ftp文件下载 在FetchFTP处理器里面定义了内部类Soc ...

  8. Heritrix 3.1.0 源码解析(十四)

    我在分析BdbFrontier对象的void schedule(CrawlURI caURI).CrawlURI next() .void finished(CrawlURI cURI)方法是,其实还 ...

  9. 锚框、交并比和非极大值抑制(tf2.0源码解析)

    锚框.交并比和非极大值抑制(tf2.0源码解析) 文章目录 锚框.交并比和非极大值抑制(tf2.0源码解析) 一.锚框生成 1.锚框的宽高 2.锚框的个数 3.注意点(★★★) 4.tf2.0代码 二 ...

最新文章

  1. java 理解break,continue,return
  2. MFC/UDP通信函数详细解说
  3. 成功要养成的习惯和改掉的习惯
  4. linux 数据复制 dd 简介
  5. python画饼图-从零开始学Python可视化(五): 饼图及环形图
  6. python向数据库传输数据时弹出not enough arguments for format string怎么办
  7. FlinkShell用kill -9杀不掉
  8. Apache Mahout:入门
  9. python的aes的ecb加密_AES ECB PKCS5/PKCS7 加密 python实现 支持中文
  10. Leetcode950. Reveal Cards In Increasing Order按递增顺序显示卡牌
  11. 【leetcode 简单】第四十一题 Excel表列序号
  12. 从程序员到软件设计师
  13. ENVI入门系列教程---一、数据预处理---1.1基本操作--5.x以后的界面(新界面)
  14. winpe装linux系统下载,winpe 安装linux
  15. 小觅双目相机如何使用_小觅双目相机测试
  16. 关系抽取论文阅读笔记
  17. 从四大云计算公司走向 看云行业趋势
  18. Linux root密码重置
  19. 聊聊我眼中恺明大神MAE的成功之处
  20. 2021年中国洋葱行业市场现状分析:洋葱价格创下近年新高[图]

热门文章

  1. 【案例3-3】查看手机配置与功能
  2. SQL Sever 各版本下载
  3. js Cracking examples
  4. excel查找在哪里_excel大神教程:5个职场人士最常用的公式,马住不谢
  5. MTK 7.1 定时发送短信功能
  6. 【一起学习输入法】华宇拼音输入法开源版本解析(9)
  7. Linux下解压tar.xz文件
  8. Windows 10 版本 21H1 发布,百度网盘下载
  9. 2.15 这样的小红书图片内容,最容易“踩雷”!【玩赚小红书】
  10. 研磨设计模式学习笔记之装饰器模式