Android Glide 3.7.0 源码解析(八) , RecyclableBufferedInputStream 的 mark/reset 实现
个人博客传送门
一、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 实现相关推荐
- Android多线程之ArrayBlockingQueue源码解析
阻塞队列系列 Android多线程之LinkedBlockingQueue源码解析 Android多线程之SynchronousQueue源码解析 Andorid多线程之DelayQueue源码分析 ...
- solrlucene3.6.0源码解析(三)
solr索引操作(包括新增 更新 删除 提交 合并等)相关UML图如下 从上面的类图我们可以发现,其中体现了工厂方法模式及责任链模式的运用 UpdateRequestProcessor相当于责任链模式 ...
- Heritrix 3.1.0 源码解析(八)
本文接着分析存储CrawlURI curi的队列容器,最重要的是BdbWorkQueue类及BdbMultipleWorkQueues类 BdbWorkQueue类继承自抽象类WorkQueue,抽象 ...
- Heritrix 3.1.0 源码解析(六)
本文分析BdbFrontier对象的相关状态和方法 BdbFrontier类继承自WorkQueueFrontier类 WorkQueueFrontier类继承自AbstractFrontier类 ...
- Heritrix 3.1.0 源码解析(十一)
上文分析了Heritrix3.1.0系统是怎么添加CrawlURI curi对象的,那么在系统初始化的时候,是怎么载入CrawlURI curi种子的呢? 我们回顾前面的文章,在我们执行采集任务的la ...
- Android多线程之IntentService源码解析
想要了解 IntentService 的工作原理需要先对 Android 系统中以 Handler.Looper.MessageQueue 组成的异步消息处理机制以及 HandlerThread 有所 ...
- Heritrix 3.1.0 源码解析(三十四)
本文主要分析FetchFTP处理器,该处理器用于ftp文件的下载,该处理器的实现是通过封装commons-net-2.0.jar组件来实现ftp文件下载 在FetchFTP处理器里面定义了内部类Soc ...
- Heritrix 3.1.0 源码解析(十四)
我在分析BdbFrontier对象的void schedule(CrawlURI caURI).CrawlURI next() .void finished(CrawlURI cURI)方法是,其实还 ...
- 锚框、交并比和非极大值抑制(tf2.0源码解析)
锚框.交并比和非极大值抑制(tf2.0源码解析) 文章目录 锚框.交并比和非极大值抑制(tf2.0源码解析) 一.锚框生成 1.锚框的宽高 2.锚框的个数 3.注意点(★★★) 4.tf2.0代码 二 ...
最新文章
- java 理解break,continue,return
- MFC/UDP通信函数详细解说
- 成功要养成的习惯和改掉的习惯
- linux 数据复制 dd 简介
- python画饼图-从零开始学Python可视化(五): 饼图及环形图
- python向数据库传输数据时弹出not enough arguments for format string怎么办
- FlinkShell用kill -9杀不掉
- Apache Mahout:入门
- python的aes的ecb加密_AES ECB PKCS5/PKCS7 加密 python实现 支持中文
- Leetcode950. Reveal Cards In Increasing Order按递增顺序显示卡牌
- 【leetcode 简单】第四十一题 Excel表列序号
- 从程序员到软件设计师
- ENVI入门系列教程---一、数据预处理---1.1基本操作--5.x以后的界面(新界面)
- winpe装linux系统下载,winpe 安装linux
- 小觅双目相机如何使用_小觅双目相机测试
- 关系抽取论文阅读笔记
- 从四大云计算公司走向 看云行业趋势
- Linux root密码重置
- 聊聊我眼中恺明大神MAE的成功之处
- 2021年中国洋葱行业市场现状分析:洋葱价格创下近年新高[图]