Okio源码地址

从读写流程来解析

  1. //初始化写入sink,
  2. Sink sink = Okio.sink(file);
  3. //初始化sink buffer,实际是 RealBufferedSink
  4. BufferedSink bufferSink = Okio.buffer(sink);
  5. bufferSink.writeUtf8("i am brucetoo");
  6. bufferSink.flush();
  7. //初始化读取source
  8. Source source = Okio.source(file);
  9. //初始化source buffer,实际是 RealBufferedSource
  10. BufferedSource bufferedSource = Okio.buffer(source);
  11. String line = bufferedSource.readUtf8();�

从上图简单例子中引入的几个关键类,大致理清其中集成,依赖关系如下:
Okio源码阅读 - 图1
Sink相关都是跟写(输出流)相关,Source都是跟读(输入流)相关,Okio封装一系列的工具方法供我们使用,避免在使用流的时候单独操作类似BufferedXXX, DataXXX, FileXXX,以及XXXStream, Reader, Writer等一系列类; 在OkioSourceSink代表基本的输入输出流的接口,对其封装的只有BufferedXXX一种类型,它提供了几乎所有 的我们常用的输入输出操作,不过在BufferedSourceBufferedSink中都包含了一个Buffer对象。

写(Sink)

  • 通过Okio.sink获取 Sink对象,直接创建:

Okio源码阅读 - 图2

  • 通过Okio.buffer(sink)获取BufferedSink对象,实际是RealBufferedSink

Okio源码阅读 - 图3

  • write 操作,RealBufferedSink其内部持有Buffer对象(使用修饰者模式中转处理所有的read,write操作,写入到segment双向链表尾部tail)

Okio源码阅读 - 图4
触发sink的write操作,即第一步截图中的回调,通过buffer读取写入到segment中的数据(读头部,写到尾部),将data数据由OutputStream写入到到流中,然后通过bufferSink.flush() -> sink.flush -> out.flush()执行刷新操作
Okio源码阅读 - 图5

读(Source)

  • 通过Okio.source方式获取到 Source对象,也是新创建,新增一个Timeout参数,主要检查读操作的超时情况

Okio源码阅读 - 图6

  • 通过Okio.buffer(source)获取BufferedSource对象,实际是RealBufferedSource

Okio源码阅读 - 图7

  • read操作也是通过内部Buffer对象来代理实现,首先通过第一步初始化的Source对应read回调将流中的数据

读到Segment中存储(见第一步截图中in.read(tail.data....)),
Okio源码阅读 - 图8
然后才是从segment存储的节点中读取数据(由于readUFT8()没有指定size,默认读取全部)
Okio源码阅读 - 图9

Buffer

Okio中所有的IO操作都是通过Buffer代理实现的,源码对其的注释是:A collection of bytes in memory(内存中的字节集合).Buffer内部只有两个成员变量,Segment(双向循环链表,每条链都是定长的字节数组)和size(buffer中存储的byte数据长度)
Okio源码阅读 - 图10

Segment

双向循环列表,引用一张图形象说明其结构,由此可以大体看出,Buffer,Segment,SegmentPool之间的联系
Okio源码阅读 - 图11

Segment具体的源码数据结构:
Okio源码阅读 - 图12

变量 解释
SIZE 单个Segment最多存储的Byte SIZE
SHARE_MINIMUM 共享segment存储的最小size,主为性能优化(避免过多内存拷贝),split方法有对应的解释
data 存储的字节数组
pos 开始读的位置
limit 开始写的位置
shared 是否data数组与其他Segment对象共享
owner 是否Segment对data数组是否拥有所有权

举个例子来说明share案例

当我们需要拷贝一份数据,刚好处于一个Segment中,为了避免拷贝,我们可以新建一个Segment对象,但是新的Segment对象与之前的Segment对象共享data数组(split方法有具体的代码逻辑),此时两个Segment对象的share属性都置为true, 而原有的Segment的ower属性为true,新建的Segment对象ower属性则为false, 此时原Segment对于数组中limit到数组结尾的空间具有写权限,而新建的Segment则没有(即不能写,也不能被回收 When a segment’s byte array is shared the segment may not be recycled, nor may its byte data be changed)。
Okio源码阅读 - 图13

移除,添加segment都是基本的链表操作
Okio源码阅读 - 图14Okio源码阅读 - 图15

当要tail segment和它前链segment写入的数据都没满一半时,考虑将tail segment的数据合并到前链的segment中,同时将tail segment回收。
Okio源码阅读 - 图16

SegmentPool

SegmentPool是一个Segment的共享池,避免创建对象和回收引起的内存抖动,该对象提供两个方法就是take()「获取对象」和recycle()「回收对象」
Okio源码阅读 - 图17

next表示当前pool有被回收的segment,赋值给result作为「新」节点segment返回,並且本身由链表下一个节点赋值,表示当前回收的segment已经被使用,所以有byteCount -= Segment.SIZE

Okio源码阅读 - 图18再看reycle方法
A. 当需要被回收的segment前置和后置节点不为null,属于异常情况(因为回收前会将前后制null)
B. 如果segment数据需要共享,则不能回收,见Share的原理和场景
C. 增加byteCount,pos/limit清空,赋值回收的segment给next

Timeout

todo..