从读写流程来解析
//初始化写入sink,
Sink sink = Okio.sink(file);
//初始化sink buffer,实际是 RealBufferedSink
BufferedSink bufferSink = Okio.buffer(sink);
bufferSink.writeUtf8("i am brucetoo");
bufferSink.flush();
//初始化读取source
Source source = Okio.source(file);
//初始化source buffer,实际是 RealBufferedSource
BufferedSource bufferedSource = Okio.buffer(source);
String line = bufferedSource.readUtf8();�
从上图简单例子中引入的几个关键类,大致理清其中集成,依赖关系如下:Sink
相关都是跟写(输出流)相关,Source
都是跟读(输入流)相关,Okio封装一系列的工具方法供我们使用,避免在使用流的时候单独操作类似BufferedXXX, DataXXX, FileXXX,以及XXXStream, Reader, Writer等一系列类; 在Okio中Source
和Sink
代表基本的输入输出流的接口,对其封装的只有BufferedXXX一种类型,它提供了几乎所有 的我们常用的输入输出操作,不过在BufferedSource
和BufferedSink
中都包含了一个Buffer
对象。
写(Sink)
- 通过
Okio.sink
获取Sink
对象,直接创建:
- 通过
Okio.buffer(sink)
获取BufferedSink
对象,实际是RealBufferedSink
- write 操作,
RealBufferedSink
其内部持有Buffer
对象(使用修饰者模式中转处理所有的read,write操作,写入到segment
双向链表尾部tail)
触发sink的write操作,即第一步截图中的回调,通过buffer
读取写入到segment
中的数据(读头部,写到尾部),将data数据由OutputStream
写入到到流中,然后通过bufferSink.flush()
-> sink.flush
-> out.flush()
执行刷新操作
读(Source)
- 通过
Okio.source
方式获取到Source
对象,也是新创建,新增一个Timeout
参数,主要检查读操作的超时情况
- 通过
Okio.buffer(source)
获取BufferedSource
对象,实际是RealBufferedSource
- read操作也是通过内部
Buffer
对象来代理实现,首先通过第一步初始化的Source对应read回调将流中的数据
读到Segment中存储(见第一步截图中in.read(tail.data....)
),
然后才是从segment存储的节点中读取数据(由于readUFT8()没有指定size,默认读取全部)
Buffer
Okio中所有的IO操作都是通过Buffer代理实现的,源码对其的注释是:A collection of bytes in memory(内存中的字节集合).Buffer内部只有两个成员变量,Segment(双向循环链表,每条链都是定长的字节数组)和size(buffer中存储的byte数据长度)
Segment
双向循环列表,引用一张图形象说明其结构,由此可以大体看出,Buffer,Segment,SegmentPool之间的联系
Segment具体的源码数据结构:
变量 | 解释 |
---|---|
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)。
移除,添加segment都是基本的链表操作
当要tail segment和它前链segment写入的数据都没满一半时,考虑将tail segment的数据合并到前链的segment中,同时将tail segment回收。
SegmentPool
SegmentPool
是一个Segment
的共享池,避免创建对象和回收引起的内存抖动,该对象提供两个方法就是take()
「获取对象」和recycle()
「回收对象」
next表示当前pool有被回收的segment,赋值给result作为「新」节点segment返回,並且本身由链表下一个节点赋值,表示当前回收的segment已经被使用,所以有byteCount -= Segment.SIZE
再看reycle
方法
A. 当需要被回收的segment前置和后置节点不为null,属于异常情况(因为回收前会将前后制null)
B. 如果segment数据需要共享,则不能回收,见Share的原理和场景
C. 增加byteCount,pos/limit清空,赋值回收的segment给next
Timeout
todo..