4.Okio
其实,上面分析的大部分类都是位于external\okhttp\okio\okio 路劲下, okio是square公司开发的开源库。
4.1 Segment
Segment是片段的意思, Segment定义如下,
final class Segment {
实际上类似于一个双向链表,部分变量如下,
static final int SIZE = 8192;// Segment维护的数组固定长度
final byte[] data;//存储数据
int pos;
int limit;// 开始和结束点的index
boolean shared;//此Segment是否被其他Segment引用即共享数据
boolean owner;//此Segment是否只被自己引用即独享数据
Segment next;// 指向前置节点
Segment prev;// 指向后置节点
其中一个构造方法如下,
Segment(byte[] data, int pos, int limit) {
this.data = data;
this.pos = pos;
this.limit = limit;
this.owner = false;
this.shared = true;
}
pop(出链表)方法如下,
public Segment pop() {
Segment result = next != this ? next : null;
prev.next = next;
next.prev = prev;
next = null;
prev = null;
return result;
}
pop方法移除了自己,首先将自己的前后两个节点连接起来,然后将自己的前后引用置空,这样就脱离了整个双向链表,然后返回next;
push(入链表)方法如下,
public Segment push(Segment segment) {
segment.prev = this;
segment.next = next;
next.prev = segment;
next = segment;
return segment;
}
push方法就是在当前和next引用中间插入一个segment进来,并且返回插入的segment.
split(分割) 方法如下,
public Segment split(int byteCount) {
if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
Segment prefix = new Segment(this);
prefix.limit = prefix.pos + byteCount;
pos += byteCount;
prev.push(prefix);
return prefix;
}
split方法从Segment中分割出一个新Segment,其中新Segment包含pos~(pos+byteCount)的数据,
原Segment包含(pos+byteCount)~limit的数据,其中数据并没有真正进行移动,通过改变pos、limit索引值,
避免了copy操作。且原Segment和新Segment的shared都被置为true,标志数据不可再进行写入改动,
此方法在Buffer的write方法中调用,主要为了实现在移动数据时直接操作Segment而不是data,这样在写数据时可以达到很高的效率。
compact(压缩)方法如下,
public void compact() {
if (prev == this) throw new IllegalStateException();
if (!prev.owner) return; // Cannot compact: prev isn't writable.
int byteCount = limit - pos;
int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
if (byteCount > availableByteCount) return; // Cannot compact: not enough writable space.
writeTo(prev, byteCount);
pop();
SegmentPool.recycle(this);
}
compact方法判断前置片段的空闲容量是否能容纳此片段的数据,如果能容纳则将此片段的数据移动到前置片段,
然后回收此片段,可以防止十分短的数据占据一整个Segment而浪费空间的现象。
writeTo(写数据)方法如下,
public void writeTo(Segment sink, int byteCount) {
if (!sink.owner) throw new IllegalArgumentException();
if (sink.limit + byteCount > SIZE) {
// We can't fit byteCount bytes at the sink's current position. Shift sink first.
if (sink.shared) throw new IllegalArgumentException();
if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException();
System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos);
sink.limit -= sink.pos;
sink.pos = 0;
}
System.arraycopy(data, pos, sink.data, sink.limit, byteCount);
sink.limit += byteCount;
pos += byteCount;
}
writeTo方法将Segment中的数据移动到sink片段中,其中的owner和Shared用来判断如果是共享片端就无法改变片段数据,
sink.limit + byteCount > SIZE 即当要写的字节大小加上原来的字节数(尾节点索引)大于Segment的最大值时抛出异常,
但是也存在一种情况就是虽然尾节点索引和写入字节大小加起来超过,但是可能是由于前面的read方法取出数据时导致pos
索引后移(pos>0),这时就先执行移动操作,使用系统的System.arraycopy方法将从pos开始的数据移动到从0开始的位置,
然后重置pos为0,limit为移动后的尾节点索引,然后再从limit位置写入数据。