转载请注明出处:http://blog.csdn.net/evan_man/article/details/51204469
上一节《OkHttp深入学习(三)——Cache》我们对okhttp中的Cache缓存机制进行了学习,学习了上一节的内容,如果叫我们自己去设计一个缓存机制,那么我们一定会有了自己的思路,想想还有点小激动。这一节我们继续来看看okhttp这个教科书中还有什么值得我们继续挖掘的东西。果不其然,我们发现了okio这个好东西,该类主要负责对Java中io的封装,使得java中的io流读写更加方便,甚至还能提高读写效率。okio项目开源地址请戳这里。在正式学习之前,我们先来了解一下它是如何使用的,随后我们再根据涉及到的内容进行深入学习。
okio的数据存储实体
okio提供了ByteString和Buffer两钟数据类型,用于存储数据。
ByteString存储的是不可变比特序列,可能你不太理解这句话,如果给出final byte[] data你是不是就懂了呢。官方文档说可以把它看成String的远方亲戚,且这个亲戚符合人体工学设计,有没有感觉很高大上。不过简单的讲它就是能够对一个byte序列(数组),以指定的编码格式进行编解码。目前支持的编解码规则有hex, base64和 UTF-8等。机智的朋友会说jav.lang.String也可以实现这些功能,是的你说的没错,ByteString只是把这些方法进行了封装,免去了我们直接输入类似”UTF-8”这样的字符串,通过直接调用byteString.utf8()获取对应的解码结果,你说哪个方便?使用前面的方法容易引入输出时的错误,在敲UTF-8这几个字符的时候可能会出现输入的错误。所以总的来讲ByteString干了一件封装的事情,它把经常用到的几种编解码方式进行了封装,不过封装的好处避免了犯错。最后ByteString对于UTF-8格式的解码还做了优化,在第一次调用utf8()方法的时候得到一个该编码的String,同时在ByteString内部保留了这个引用 ,当再次调用utf8()的时候则直接返回这个引用。
Buffer存储的是可变比特序列,需要注意的是Buffer内部对比特数据的存储不是直接使用一个byte数组那么简单,它使用了一种新的数据类型Segment进行存储。不过我们先不去管Segment是什么东西,可以先直接将Buffer想象成一个ArrayList集合就可以了,之所以做这样的想象是因为Buffer的容量可以动态拓展,从序列的尾部存入数据,从序列的头部读取数据。其实Buffer的底层实现远比ArrayList复杂的多,它使用的是一个双向链表的形式存储数据,链表结点的数据类型就是前面说的Segment,Segment中存储有一个不可变比特序列,即final byte[] data。使用Buffer的好处在于在从一个Buffer移动到另一个Buffer的时候,实际上并没有对比特序列进行拷贝,只是改变了对应Segment的所有者,其实这也采用链表存储数据的好处,这样的特点在多线程网络通信中会带来很大的好处。最后使用Buffer还有另一个好处那就是它实现了BufferedSource和BufferedSink接口,这两个接口我们后面再讲,主要是实现了形如nextInt等方法,方便从buffer中读取数据,否则Buffer中存储的byte数据我们并不能直接拿来使用。
上面的文字有点多,我们对ByteString和Buffer做个小节。ByteString存储了一个final byte[] data比特数组,通过调用ByteString的相关方法对存储的比特数据进行相应的编解码,常用编解码有UTF-8、Base64和hex。Buffer用双向链表形式存储了一系列的Segment结点,Segment结点中存储final byte[] data比特数组;Buffer实现了BufferedSource和BufferedSink接口,通过调用形如buffer.readXX()方法将比特数组进行解码,返回XX类型的数据,就像scanner.nextInt一样使用。
okio的输入输出流
接下来看看okio中与java sdk中的InputStream和OutPutStream两个对应的接口,Sink和Source。
官方认为sink和source接口相对于java sdk的inputStream和OutputStream更加容易实现,定义sink和source两个接口的作者认为,sdk中的available()和读写单字节的方法纯属鸡肋。
- Sink定义了四个方法write(Buffer source, long byteCount), flush(), close(), and timeout();
- Source定义了三个方法long read(Buffer sink, long byteCount), close(), and timeout();
虽然Sink和Source只定义了很少的方法,这也是为何说它容易实现的原因,但是我们在使用过程中,并不直接拿它进行使用,而是使用BufferedSink和BufferedSource对前面的接口进行再度的封装,BufferedSink和BufferedSource接口定义了一系列好用的方法。
- BufferedSink定义了writeUtf8、writeString、writeByte、writeShort、 writeInt、writeLong等常用方法;
- BufferedSource定义了readByte()、readShort()、readInt()、readLong()、readUtf8()等常用方法;
最后okio的作者认为,java的sdk对字节流和字符流进行分开定义这一事情,并不是那么优雅,特此okio并不进行这样的划分。具体做法就是把比特数据都交给Buffer管理,然后Buffer实现BufferedSource和BufferedSink这两个接口,最后通过调用buffer相应的方法对数据进行编解码。
okio的实例
- private static final ByteString PNG_HEADER = ByteString.decodeHex(“89504e470d0a1a0a”);
- public void decodePng(InputStream in) throws IOException {
- BufferedSource pngSource = Okio.buffer(Okio.source(in)); //note1
- ByteString header = pngSource.readByteString(PNG_HEADER.size()); //note2
- if (!header.equals(PNG_HEADER)) {
- throw new IOException(“Not a PNG.”);
- }
- while (true) {
- Buffer chunk = new Buffer(); //note3
- // Each chunk is a length, type, data, and CRC offset.
- int length = pngSource.readInt(); //note4
- String type = pngSource.readUtf8(4);
- pngSource.readFully(chunk, length); //note5
- int crc = pngSource.readInt();
- decodeChunk(type, chunk);
- if (type.equals(“IEND”)) break;
- }
- pngSource.close(); //note7
- }
- private void decodeChunk(String type, Buffer chunk) {
- if (type.equals(“IHDR”)) {
- int width = chunk.readInt(); //note6
- int height = chunk.readInt();
- System.out.printf(”%08x: %s %d x %d%n”, chunk.size(), type, width, height);
- } else {
- System.out.printf(”%08x: %s%n”, chunk.size(), type);
- }
- }
private static final ByteString PNG_HEADER = ByteString.decodeHex("89504e470d0a1a0a");
public void decodePng(InputStream in) throws IOException {
BufferedSource pngSource = Okio.buffer(Okio.source(in)); //note1
ByteString header = pngSource.readByteString(PNG_HEADER.size()); //note2
if (!header.equals(PNG_HEADER)) {
throw new IOException("Not a PNG.");
}
while (true) {
Buffer chunk = new Buffer(); //note3
// Each chunk is a length, type, data, and CRC offset.
int length = pngSource.readInt(); //note4
String type = pngSource.readUtf8(4);
pngSource.readFully(chunk, length); //note5
int crc = pngSource.readInt();
decodeChunk(type, chunk);
if (type.equals("IEND")) break;
}
pngSource.close(); //note7
}
private void decodeChunk(String type, Buffer chunk) {
if (type.equals("IHDR")) {
int width = chunk.readInt(); //note6
int height = chunk.readInt();
System.out.printf("%08x: %s %d x %d%n", chunk.size(), type, width, height);
} else {
System.out.printf("%08x: %s%n", chunk.size(), type);
}
}
1、将InputStream转换为BufferedSource对象
2、将指定长度的比特数据转换成ByteString数据
3、创建Buffer对象
4、从第一步获取到的BufferedSource中读取到int和UTF8数据
5、读取固定长度的数据到Buffer中
6、Buffer的读写跟BufferedSource和BufferedSink一样,因为它们实现了同样的接口
7、关闭第一步得到的输出流。
总的来讲okio的使用就是首先创建一个对应的BufferedSource和BufferedSink对象,随后利用相关方法,获取或写入ByteString、Buffer、int、long等类型数据,对于Buffer数据可以通过调用与BufferedSource和BufferedSink一样的方法对已经读取到的数据进行进一步的解析。
okio的原理介绍
上面我们对于okio的基本情况进行了介绍,同时给出了一个简答的使用案例。本小节将对底层的实现进行介绍。介绍的内容以前面的几个小节涉及到的内容为主,首先对ByteString、Buffer两个数据类型介绍,随后对Okio.buffer和Okio.source两个方法进行介绍。
ByteString
按照以往的惯例,首先看看该对象都有哪些域:
- static final char[] HEX_DIGITS = { ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’ }; //很明显这个肯定是在进行hex解析的时候被使用到
- final byte[] data;
- transient int hashCode; //说是String远方亲戚也不假,String也有类似上面的两个域
- transient String utf8; //这个域的存在就是实现了对utf-8解码的优化,是一个对utf8解码String的优化
static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; //很明显这个肯定是在进行hex解析的时候被使用到
final byte[] data;
transient int hashCode; //说是String远方亲戚也不假,String也有类似上面的两个域
transient String utf8; //这个域的存在就是实现了对utf-8解码的优化,是一个对utf8解码String的优化
ByteString()@ByteString.class
构造器
的参数为
一个byte[]类型,不过该构造器只能被同包的类使用,因此我们创建ByteString对象并不是通过该方法。
我们是如何构造一个ByteString对象?看下面的方法
of@ByteString.class
- public static ByteString of(byte… data) {
- if (data == null) throw new IllegalArgumentException(“data == null”);
- return new ByteString(data.clone());//note1
- }
- public static ByteString of(byte[] data, int offset, int byteCount) {
- if (data == null) throw new IllegalArgumentException(“data == null”);
- checkOffsetAndCount(data.length, offset, byteCount);//note2
- byte[] copy = new byte[byteCount];
- System.arraycopy(data, offset, copy, 0, byteCount);//note3
- return new ByteString(copy);
- }
public static ByteString of(byte... data) {
if (data == null) throw new IllegalArgumentException("data == null");
return new ByteString(data.clone());//note1
}
public static ByteString of(byte[] data, int offset, int byteCount) {
if (data == null) throw new IllegalArgumentException("data == null");
checkOffsetAndCount(data.length, offset, byteCount);//note2
byte[] copy = new byte[byteCount];
System.arraycopy(data, offset, copy, 0, byteCount);//note3
return new ByteString(copy);
}
1、调用clone方法,重新创建一个byte数组,clone一个数组的原因很简单,我们确保ByteString的data域指向的byte[]没有被其它对象所引用,否则就容易破坏ByteString中存储的是一个不可变比特流数据这一约束。
2、边界检查
3、老朋友了,将data中指定的数据拷贝到copy数组中去。
接下来对ByteString的相关方法进行介绍,在此并不准备对byteString中的所有方法进行介绍,只是介绍几个常用的方法。
toString()@ByteString.class
- public String toString() {
- if (data.length == 0) {
- return “ByteString[size=0]”;
- }
- if (data.length <= 16) {
- return String.format(“ByteString[size=%s data=%s]”, data.length, hex());
- }
- return String.format(“ByteString[size=%s md5=%s]”, data.length, md5().hex());
- }
public String toString() {
if (data.length == 0) {
return "ByteString[size=0]";
}
if (data.length <= 16) {
return String.format("ByteString[size=%s data=%s]", data.length, hex());
}
return String.format("ByteString[size=%s md5=%s]", data.length, md5().hex());
}
很简单,这就不讲了,将data数据分别以hex和md5格式打印
utf8()@ByteString.class
- public String utf8() {
- String result = utf8;
- return result != null ? result : (utf8 = new String(data, Util.UTF_8));
- }
public String utf8() {
String result = utf8;
return result != null ? result : (utf8 = new String(data, Util.UTF_8));
}
这里的一个判断语句,实现ByteString性能的优化,看来优化这个东西还是很容易实现的嘛。第一次创建UTF-8对象的方法是调用new String(data, Util.UTF_8),后面就不再调用该方法而是直接返回result;发现utf8就是对String的方法进一步封装,ByteString中很多其它的方法也类似,在此就不讲了。
在正式介绍Buffer之前我们先来了解一下Segment。
Segment.class
需要注意的是这是一个双向链表结构!!
按照以往的惯例,首先看看该对象都有哪些域:
- static final int SIZE = 2048; //一个Segment存储的最大比特数据的数量
- final byte[] data; //比特数组的引用
- int pos; //pos第一个可以读的位置
- int limit; //limit是第一个可以写的位置,所以一个Segment的可读数据数量为pos~limit-1=limit-pos;limit和pos的有效值为0~SIZE-1
- boolean shared; //当前存储的data数据是其它对象共享的则为真
- boolean owner; //是当前data的所有者
- Segment next; //下一个Segment
- Segment prev; //前一个Segment
static final int SIZE = 2048; //一个Segment存储的最大比特数据的数量
final byte[] data; //比特数组的引用
int pos; //pos第一个可以读的位置
int limit; //limit是第一个可以写的位置,所以一个Segment的可读数据数量为pos~limit-1=limit-pos;limit和pos的有效值为0~SIZE-1
boolean shared; //当前存储的data数据是其它对象共享的则为真
boolean owner; //是当前data的所有者
Segment next; //下一个Segment
Segment prev; //前一个Segment
Segment()@Segment.class
- Segment() {
- this.data = new byte[SIZE];
- this.owner = true; //note1
- this.shared = false; //note2
- }
- Segment(Segment shareFrom) {
- this(shareFrom.data, shareFrom.pos, shareFrom.limit);
- shareFrom.shared = true; //note3
- }
- Segment(byte[] data, int pos, int limit) {
- this.data = data;
- this.pos = pos;
- this.limit = limit;
- this.owner = false; //note4
- this.shared = true; //note5
- }
Segment() {
this.data = new byte[SIZE];
this.owner = true; //note1
this.shared = false; //note2
}
Segment(Segment shareFrom) {
this(shareFrom.data, shareFrom.pos, shareFrom.limit);
shareFrom.shared = true; //note3
}
Segment(byte[] data, int pos, int limit) {
this.data = data;
this.pos = pos;
this.limit = limit;
this.owner = false; //note4
this.shared = true; //note5
}
1、采用该构造器表明该数据data的所有者是该Segment,故owner为真
2、数据不是来自其它对象,所以shared为假
3、数据来自其它的Segment,设置参数Segment的Shared为真,表明该Segment数据被别人共享了
4、数据是来自其它对象,所以shared为真
pop()@Seg
ment.class
将当前Segment从Segment链中移除出去。
返回参数Segment的后一个Segment
push()@Segment.class
- public Segment push(Segment segment) {
- segment.prev = this;
- segment.next = next;
- next.prev = segment;
- next = segment;
- return segment;
- }
public Segment push(Segment segment) {
segment.prev = this;
segment.next = next;
next.prev = segment;
next = segment;
return segment;
}
将参数中的Segment压人调用该方法的Segment结点后面。返回刚刚压入的Segment
split()@Segment.class
- 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;
- }
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;
}
该方法用于将Segment一分为二,将pos+1~pos+byteCount-1的内容给新的Segment,将pos+byteCount~limit-1的内容留给自己,然后将前面的新Segment插入到自己前面。这里需要注意的是,虽然这里变成了两个Segment但是实际上byte[]数据并没有被拷贝,两个Segment都引用该Segment。
下面介绍的这个方法尝试将当前Segment和它之前的Segment进行合并,目的是减少Segment数量,如果没有任何异常出现的话,结果就是将调用该方法的Segment从Segment链中被移除出去。
compact()@Segment.class
- public void compact() {
- if (prev == this) throw new IllegalStateException();
- if (!prev.owner) return; //note1
- int byteCount = limit - pos; //note2
- int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos); //note3
- if (byteCount > availableByteCount) return; //note4
- writeTo(prev, byteCount); //note5
- pop(); //note6
- SegmentPool.recycle(this); //note7
- }
public void compact() {
if (prev == this) throw new IllegalStateException();
if (!prev.owner) return; //note1
int byteCount = limit - pos; //note2
int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos); //note3
if (byteCount > availableByteCount) return; //note4
writeTo(prev, byteCount); //note5
pop(); //note6
SegmentPool.recycle(this); //note7
}
1、如果当前Segment前结点不是自己,且前结点具备可写性。则进行下列操作,或者说下列操作的前提是当前结点的前结点可以写入数据
2、记录当前Segment具有的数据,数据大小为limit-pos-1;
3、统计前结点是否被共享,如果共享则只记录Size-limit大小,如果没有被共享,则加上pre.pos之前的空位置;
4、判断pre拥有的空余位置是否够将当前Segment的全部数据存入进来;
5、将当前Segment中数据写入pre中
6、将当前Segment从Segment链表中移除
7、回收该Segment
下面这个方法的作用就是将调用该方法的Segment中的byteCount个数据写入到方法参数的Segment中。
writeTo()@Segment.class
- public void writeTo(Segment sink, int byteCount) {
- if (!sink.owner) throw new IllegalArgumentException(); //note1
- if (sink.limit + byteCount > SIZE) { //note2
- if (sink.shared) throw new IllegalArgumentException(); //note3
- if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException(); //note4
- System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos); //note5
- sink.limit -= sink.pos;
- sink.pos = 0;
- }
- System.arraycopy(data, pos, sink.data, sink.limit, byteCount);//note6
- sink.limit += byteCount;
- pos += byteCount;
- }
public void writeTo(Segment sink, int byteCount) {
if (!sink.owner) throw new IllegalArgumentException(); //note1
if (sink.limit + byteCount > SIZE) { //note2
if (sink.shared) throw new IllegalArgumentException(); //note3
if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException(); //note4
System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos); //note5
sink.limit -= sink.pos;
sink.pos = 0;
}
System.arraycopy(data, pos, sink.data, sink.limit, byteCount);//note6
sink.limit += byteCount;
pos += byteCount;
}
1、首先判断参数sink是否具有更改数据的权限,没有则抛出异常,运行时异常不需要捕获
2、如果当前写入的数据在limit~SIZE-1之间得不到满足,意味着需要先将sink中的数据移动到字节数组的最前端,因此需要进行下面的判断,判断是否可移动不能则抛出异常
3、当前Fragment处于可共享的状态,抛出异常
4、条件转换为 byteCount > SIZE-(sink.limit-
sink.pos),意味着当前Segment没有足够的空间写入byteCount数据
5、
这里我们来复习一下arraycopy方法public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
将src中的srcPos~srcPos+length-1的数据复制到dest中的destPos~destPos+length-1位置处; 该方法很重要只要涉及到数组的移动,最底层都是调用该方法。
那么note5中的含义就是将pos~limit-1之间的数据移动到0~limit-pos-1位置处,并设置Segment.limit值为limit-pos,sink.pos设置为0;
6、将当前Segment的pos~pos+byteCount-1之间的数据复制到sink的limit~limit+byteCount-1之间,同时设置sink.limit和当前Segment的pos值。
到此为止我们对于Segment的分析就结束了,但是在我们正式介绍Buffer之前,还需要介绍一下SegmentPool这个类,在compact()方法的最后我们调用egmentPool.recycle(this);方法对该Segment资源进行回收。
SegmentPool.class
按照以往的惯例,首先看看该对象都有哪些域:
- static final long MAX_SIZE = 64 * 1024; // 大家是否还记得一个Segment记录的数据最大长度为2048?因此该Segment相当于能存储32个Segment对象。不过为何不是32*2048?
- static Segment next; //该SegmentPool存储了一个回收Segment的链表
- static long byteCount; //该值记录当前存储的所有Segment总大小,最大值为MAX_SIZE
static final long MAX_SIZE = 64 * 1024; // 大家是否还记得一个Segment记录的数据最大长度为2048?因此该Segment相当于能存储32个Segment对象。不过为何不是32*2048?
static Segment next; //该SegmentPool存储了一个回收Segment的链表
static long byteCount; //该值记录当前存储的所有Segment总大小,最大值为MAX_SIZE
take()@SegmentPool.class
- static Segment take() {
- synchronized (SegmentPool.class) {
- if (next != null) {
- Segment result = next;
- next = result.next;
- result.next = null;
- byteCount -= Segment.SIZE;
- return result;
- }
- }
- return new Segment(); // Pool is empty. Don’t zero-fill while holding a lock.
- }
static Segment take() {
synchronized (SegmentPool.class) {
if (next != null) {
Segment result = next;
next = result.next;
result.next = null;
byteCount -= Segment.SIZE;
return result;
}
}
return new Segment(); // Pool is empty. Don't zero-fill while holding a lock.
}
方法很简单,就是尝试从SegmentPool的Segment链表中取出一个Segment对象,如果链表为空则创建一个Segment对象返回。
recycle()@SegmentPool.class
- static void recycle(Segment segment) {
- if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
- if (segment.shared) return; // This segment cannot be recycled.
- synchronized (SegmentPool.class) {
- if (byteCount + Segment.SIZE > MAX_SIZE) return; // Pool is full.
- byteCount += Segment.SIZE;
- segment.next = next;
- segment.pos = segment.limit = 0;
- next = segment;
- }
- }
static void recycle(Segment segment) {
if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
if (segment.shared) return; // This segment cannot be recycled.
synchronized (SegmentPool.class) {
if (byteCount + Segment.SIZE > MAX_SIZE) return; // Pool is full.
byteCount += Segment.SIZE;
segment.next = next;
segment.pos = segment.limit = 0;
next = segment;
}
}
方法同样很简单,就是尝试将参数的Segment对象加入到自身的Segment链表中,如果SegmentPool已经满了,则直接抛弃该Segment,否则加入到链表中。
细心的同学肯定发现,SegmentPool中的域和方法都是static修饰的,原因很简单,SegmentPool的作用就是管理多余的Segment,不直接丢弃废弃的Segment,等客户需要Segment的时候直接从该池中获取一个对象,避免了重复创建新对象,提高资源利用率。
在正式进入Buffer内容前我们先梳理一下,我们期望从中了解的内容,我们参照ByteString部分的讲解,这里我们需要学习和了解如何构建一个Buffer对象,Buffer中的数据如何管理、存储,Buffer中的buffer.readXX()方法底层是如何实现的。有了这些目标我们就来分析一下Buffer这个类。
Buffer.class
首先看看该对象都有哪些域:
- static final char[] HEX_DIGITS = { ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’ }; //很明显这个肯定是在进行hex解析的时候被使用到
- Segment head; //Buffer存储了一个这样的head结点,这就是Buffer对数据的存储结构。字节数组都是交给Segment进行管理。
- long size; //当前存储的数据的大小
static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; //很明显这个肯定是在进行hex解析的时候被使用到
Segment head; //Buffer存储了一个这样的head结点,这就是Buffer对数据的存储结构。字节数组都是交给Segment进行管理。
long size; //当前存储的数据的大小
Buffer()@Buffer.class
构造器是空的!不过想想构造器是空的也不足为奇,因为即使构造器是空的,其实域中的值也早就被赋了值,即segment=null,size=0;
前面一直讲Buffer到Buffer之间的移动数据效率是如何如何的牛逼,这里我们就一探究竟
copyTo()@@Buffer.class
- public Buffer copyTo(Buffer out, long offset, long byteCount) {
- if (out == null) throw new IllegalArgumentException(“out == null”);
- checkOffsetAndCount(size, offset, byteCount);
- if (byteCount == 0) return this;
- out.size += byteCount; //note1
- Segment s = head;
- for (; offset >= (s.limit - s.pos); s = s.next) { //note2
- offset -= (s.limit - s.pos);
- }
- // note3
- for (; byteCount > 0; s = s.next) {
- Segment copy = new Segment(s);
- copy.pos += offset;
- copy.limit = Math.min(copy.pos + (int) byteCount, copy.limit);
- if (out.head == null) {
- out.head = copy.next = copy.prev = copy;
- } else {
- out.head.prev.push(copy);
- }
- byteCount -= copy.limit - copy.pos;
- offset = 0;
- }
- return this;
- }
public Buffer copyTo(Buffer out, long offset, long byteCount) {
if (out == null) throw new IllegalArgumentException("out == null");
checkOffsetAndCount(size, offset, byteCount);
if (byteCount == 0) return this;
out.size += byteCount; //note1
Segment s = head;
for (; offset >= (s.limit - s.pos); s = s.next) { //note2
offset -= (s.limit - s.pos);
}
// note3
for (; byteCount > 0; s = s.next) {
Segment copy = new Segment(s);
copy.pos += offset;
copy.limit = Math.min(copy.pos + (int) byteCount, copy.limit);
if (out.head == null) {
out.head = copy.next = copy.prev = copy;
} else {
out.head.prev.push(copy);
}
byteCount -= copy.limit - copy.pos;
offset = 0;
}
return this;
}
1、更改目标Buffer的size值
2、求出从当前Buffer的head需要扩过多少个segment才能能够满足offset,之后将对该s往后的byteCount个数据进行拷贝
3、每次插入一个Segment节点,实际创建的new Fragment并不是真的创建一个Segment对象,而是将Segment中的数据进行共享。
4、注意!!Buffer中建立的链表是一个双向链表结构,所以out.head.prev等价于获取到了buffer双向链表的尾部Segment节点,随后在该尾部节点后插入新得到Segment。
下面我们最后看一个readInt方法
readInt()@Buffer.class
- public int readInt() {
- if (size < 4) throw new IllegalStateException(“size < 4: ” + size); //note1
- Segment segment = head;
- int pos = segment.pos;
- int limit = segment.limit;
- //note2
- if (limit - pos < 4) {
- return (readByte() & 0xff) << 24
- | (readByte() & 0xff) << 16
- | (readByte() & 0xff) << 8
- | (readByte() & 0xff);
- }
- //note3
- byte[] data = segment.data;
- int i = (data[pos++] & 0xff) << 24
- | (data[pos++] & 0xff) << 16
- | (data[pos++] & 0xff) << 8
- | (data[pos++] & 0xff);
- size -= 4;
- if (pos == limit) { //note4
- head = segment.pop();
- SegmentPool.recycle(segment);
- } else {
- segment.pos = pos;
- }
- return i; //note5
- }
public int readInt() {
if (size < 4) throw new IllegalStateException("size < 4: " + size); //note1
Segment segment = head;
int pos = segment.pos;
int limit = segment.limit;
//note2
if (limit - pos < 4) {
return (readByte() & 0xff) << 24
| (readByte() & 0xff) << 16
| (readByte() & 0xff) << 8
| (readByte() & 0xff);
}
//note3
byte[] data = segment.data;
int i = (data[pos++] & 0xff) << 24
| (data[pos++] & 0xff) << 16
| (data[pos++] & 0xff) << 8
| (data[pos++] & 0xff);
size -= 4;
if (pos == limit) { //note4
head = segment.pop();
SegmentPool.recycle(segment);
} else {
segment.pos = pos;
}
return i; //note5
}
1、很明显一个int数据的字节数是4,所以必须保证当前buffer的size大于4
2、当前的Segment所包含的字节数小于4,因此还需要去下一个Segment中获取一部分数据,因此通过调用readByte()方法一字节一个字节的读取,该方法我们后面进行介绍。
3、当前的Segment数据够用,因此直接从pos位置起读取4个字节数据,然后将其转换为int数据,转换方式很简单就是进行移位和或运算
4、如果pos==limit证明当前head对应的Segment没有可读数据,因此将该Segment从双向链表中移除出去,并回收该Segment。如果还有数据则刷新Segment的pos值。
5、返回解析得到的int值
readByte()@Buffer.class
- public byte readByte() {
- if (size == 0) throw new IllegalStateException(“size == 0”);
- Segment segment = head;
- int pos = segment.pos;
- int limit = segment.limit;
- byte[] data = segment.data;
- byte b = data[pos++]; //note1
- size -= 1;
- if (pos == limit) { //note2
- head = segment.pop();
- SegmentPool.recycle(segment);
- } else {
- segment.pos = pos;
- }
- return b; //note3
- }
public byte readByte() {
if (size == 0) throw new IllegalStateException("size == 0");
Segment segment = head;
int pos = segment.pos;
int limit = segment.limit;
byte[] data = segment.data;
byte b = data[pos++]; //note1
size -= 1;
if (pos == limit) { //note2
head = segment.pop();
SegmentPool.recycle(segment);
} else {
segment.pos = pos;
}
return b; //note3
}
1、从当前Segment中获取一个字节数据
2、如果pos==limit证明当前head对应的Segment没有可读数据,因此将该Segment从双向链表中移除出去,并回收该Segment,如果还有数据则刷新Segment的pos值。
3、返回读到的字节数据
到此为止我们对okio的数据存储内容就介绍到这里,下面我们对Okio.buffer、Okio.source、Okio.sink这几个方法进行介绍。
Okio.class
Okio为我们提供了如下的方法获取一个Source对象:共计5个方法
- Source source(final InputStream in) { return source(in, new Timeout());}
- Source source(File file) throws FileNotFoundException{ return source(new FileInputStream(file)); }
- Source source(Path path, OpenOption… options) throws IOException { return source(Files.newInputStream(path, options)); }
- Source source(final Socket socket) throws IOException{
- AsyncTimeout timeout = timeout(socket);
- Source source = source(socket.getInputStream(), timeout);
- return timeout.source(source);
- }
Source source(final InputStream in) { return source(in, new Timeout());}
Source source(File file) throws FileNotFoundException{ return source(new FileInputStream(file)); }
Source source(Path path, OpenOption... options) throws IOException { return source(Files.newInputStream(path, options)); }
Source source(final Socket socket) throws IOException{
AsyncTimeout timeout = timeout(socket);
Source source = source(socket.getInputStream(), timeout);
return timeout.source(source);
}
上面的无论参数是path、file、socket、InputStream最终都是会调用下面的方法创建一个Source对象
Source source(final InputStream in, final Timeout timeout){ }
source()@Okio.class
- private static Source source(final InputStream in, final Timeout timeout) {
- if (in == null) throw new IllegalArgumentException(“in == null”);
- if (timeout == null) throw new IllegalArgumentException(“timeout == null”);
- return new Source() {
- @Override public long read(Buffer sink, long byteCount) throws IOException {
- if (byteCount < 0) throw new IllegalArgumentException(“byteCount < 0: ” + byteCount);
- if (byteCount == 0) return 0;
- timeout.throwIfReached();//note1
- Segment tail = sink.writableSegment(1); //note2
- int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit); //note3
- int bytesRead = in.read(tail.data, tail.limit, maxToCopy); //note4
- if (bytesRead == -1) return -1;
- tail.limit += bytesRead;//note5
- sink.size += bytesRead;
- return bytesRead;
- }
- @Override public void close() throws IOException {
- in.close();
- }
- @Override public Timeout timeout() {
- return timeout;
- }
- @Override public String toString() {
- return “source(“ + in + “)”;
- }
- };
- }
private static Source source(final InputStream in, final Timeout timeout) {
if (in == null) throw new IllegalArgumentException("in == null");
if (timeout == null) throw new IllegalArgumentException("timeout == null");
return new Source() {
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (byteCount == 0) return 0;
timeout.throwIfReached();//note1
Segment tail = sink.writableSegment(1); //note2
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit); //note3
int bytesRead = in.read(tail.data, tail.limit, maxToCopy); //note4
if (bytesRead == -1) return -1;
tail.limit += bytesRead;//note5
sink.size += bytesRead;
return bytesRead;
}
@Override public void close() throws IOException {
in.close();
}
@Override public Timeout timeout() {
return timeout;
}
@Override public String toString() {
return "source(" + in + ")";
}
};
}
1、检查当前线程是否被中断,是否触及到了deadLine;满足任意一个条件都会抛出异常
2、从sink(Buffer对象)中获取得到可以写入一个字符的第一个Segment
3、在Segment剩余写入空间和目标写入数byteCount之间选择最小的数,结果为从InputStream读入的最大数据量。
4、从InputStream中读入最多maxToCopy数量的字节到tail.data中,写入位置为tail.limit;
5、对buffer的Segment的值进行刷新;返回读取字节数
Okio为我们提供了如下的方法获取一个Sink对象:共计6个方法
- Sink sink(final OutputStream out) { return sink(out, new Timeout()); }
- Sink sink(File file) throws FileNotFoundException{ return sink(new FileOutputStream(file)); }
- Sink appendingSink(File file) throws FileNotFoundException{ return sink(new FileOutputStream(file, true)); }
- Sink sink(Path path, OpenOption… options) throws IOException{ return sink(Files.newOutputStream(path, options)); }
- Sink sink(final Socket socket) throws IOException{
- AsyncTimeout timeout = timeout(socket);
- Sink sink = sink(socket.getOutputStream(), timeout);
- return timeout.sink(sink);
- }
Sink sink(final OutputStream out) { return sink(out, new Timeout()); }
Sink sink(File file) throws FileNotFoundException{ return sink(new FileOutputStream(file)); }
Sink appendingSink(File file) throws FileNotFoundException{ return sink(new FileOutputStream(file, true)); }
Sink sink(Path path, OpenOption... options) throws IOException{ return sink(Files.newOutputStream(path, options)); }
Sink sink(final Socket socket) throws IOException{
AsyncTimeout timeout = timeout(socket);
Sink sink = sink(socket.getOutputStream(), timeout);
return timeout.sink(sink);
}
上面的无论参数是path、file、socket、OutputStream最终都是会调用下面的方法创建一个Sink对象
Sink sink(final OutputStream out, final Timeout timeout){ }
sink()@Okio.class
- public static Sink sink(final OutputStream out) {
- return sink(out, new Timeout());
- }
- private static Sink sink(final OutputStream out, final Timeout timeout) {
- if (out == null) throw new IllegalArgumentException(“out == null”);
- if (timeout == null) throw new IllegalArgumentException(“timeout == null”);
- return new Sink() {
- @Override public void write(Buffer source, long byteCount) throws IOException {
- checkOffsetAndCount(source.size, 0, byteCount);
- while (byteCount > 0) {
- timeout.throwIfReached();
- Segment head = source.head; //note1
- int toCopy = (int) Math.min(byteCount, head.limit - head.pos);//note2
- out.write(head.data, head.pos, toCopy); //note3
- head.pos += toCopy;//note4
- byteCount -= toCopy;
- source.size -= toCopy;
- if (head.pos == head.limit) {//note4
- source.head = head.pop();
- SegmentPool.recycle(head);
- }
- }
- }
- @Override public void flush() throws IOException {
- out.flush();
- }
- @Override public void close() throws IOException {
- out.close();
- }
- @Override public Timeout timeout() {
- return timeout;
- }
- @Override public String toString() {
- return “sink(“ + out + “)”;
- }
- };
- }
public static Sink sink(final OutputStream out) {
return sink(out, new Timeout());
}
private static Sink sink(final OutputStream out, final Timeout timeout) {
if (out == null) throw new IllegalArgumentException("out == null");
if (timeout == null) throw new IllegalArgumentException("timeout == null");
return new Sink() {
@Override public void write(Buffer source, long byteCount) throws IOException {
checkOffsetAndCount(source.size, 0, byteCount);
while (byteCount > 0) {
timeout.throwIfReached();
Segment head = source.head; //note1
int toCopy = (int) Math.min(byteCount, head.limit - head.pos);//note2
out.write(head.data, head.pos, toCopy); //note3
head.pos += toCopy;//note4
byteCount -= toCopy;
source.size -= toCopy;
if (head.pos == head.limit) {//note4
source.head = head.pop();
SegmentPool.recycle(head);
}
}
}
@Override public void flush() throws IOException {
out.flush();
}
@Override public void close() throws IOException {
out.close();
}
@Override public Timeout timeout() {
return timeout;
}
@Override public String toString() {
return "sink(" + out + ")";
}
};
}
1、获取数据源buffer的第一个Segment
2、在Segment的可读数据和预期写入字节数之间选择一个最小值
3、将Segment的pos位置处到toCopy之间的数据写入到输出流中
4、判断Segment值是否全部读取完毕,完毕则将该Segment从buffer中移出,并将移出的Segment进行回收
RealBufferedSource.class
本节的最后我们在看看buffer(Source source)和buffer(Sink sink)这两个方法
BufferedSource buffer()@Okio.class
- public static BufferedSource buffer(Source source) {
- if (source == null) throw new IllegalArgumentException(“source == null”);
- return new RealBufferedSource(source);
- }
public static BufferedSource buffer(Source source) {
if (source == null) throw new IllegalArgumentException("source == null");
return new RealBufferedSource(source);
}
所以Okio的buffer方法实际上创建的是一个RealBufferedSource对象,下面我们看其构造器的内容
RealBufferedSource()@RealBufferedSource.class
- public RealBufferedSource(Source source) {
- this(source, new Buffer()); //note1
- }
- public RealBufferedSource(Source source, Buffer buffer) {
- if (source == null) throw new IllegalArgumentException(“source == null”);
- this.buffer = buffer;
- this.source = source;
- }
public RealBufferedSource(Source source) {
this(source, new Buffer()); //note1
}
public RealBufferedSource(Source source, Buffer buffer) {
if (source == null) throw new IllegalArgumentException("source == null");
this.buffer = buffer;
this.source = source;
}
1、通过new RealBufferedSource(source); 创建的RealBufferedSource对象,系统还会自动的给其创建一个Buffer对象。RealBufferedSource对象的创建到此为止就结束了,但是为了内容的健全,这里再对RealBufferedSource的个别方法进行简单介绍。使用RealBufferedSource对Source进行包装的目的在于,RealBufferedSource提供了很多好用的方法,如readXX,同时既然使用了BufferedSource这个名字,意味着它一次可能读取多个数据,提高I/O读写效率,因此下面我们分别看下它read方法和readInt方法。
read()@RealBufferedSource.class
- public long read(Buffer sink, long byteCount) throws IOException {
- if (sink == null) throw new IllegalArgumentException(“sink == null”);
- if (byteCount < 0) throw new IllegalArgumentException(“byteCount < 0: ” + byteCount);
- if (closed) throw new IllegalStateException(“closed”);
- if (buffer.size == 0) { //note1
- long read = source.read(buffer, Segment.SIZE);
- if (read == -1) return -1;
- }
- long toRead = Math.min(byteCount, buffer.size); //note2
- return buffer.read(sink, toRead); //note3
- }
public long read(Buffer sink, long byteCount) throws IOException {
if (sink == null) throw new IllegalArgumentException("sink == null");
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
if (buffer.size == 0) { //note1
long read = source.read(buffer, Segment.SIZE);
if (read == -1) return -1;
}
long toRead = Math.min(byteCount, buffer.size); //note2
return buffer.read(sink, toRead); //note3
}
1、如果当前buffer为空,则通过source的read方法读取最多Segment.Size个数据。
2、从Segment.SIZE和目标读取数中取出的最小值
3、从buffer中取出上面得到的toRead个字节数据
所以RealBufferedSource的缓存机制(提高I/O读写效率的方式)为当buffer为空时从Source中读取最多2048个字节数,对于多次调用read方法读取少量的字节的情况,很可能只进行一次真实的I/O流操作,大多数情况是从buffer读取数据。
接着我们查看一下readInt方法
readInt()@RealBufferedSource.class
public int readInt() throws IOException {
require(4);
return buffer.readInt();
}
喂喂喂,那位同学里下巴掉了诶。对方法就是这么偷懒,RealBufferedSource结果就是调用buffer的readInt方法。不过require(4)方法第一次见,我们进入看看。
require()@RealBufferedSource.class
- public void require(long byteCount) throws IOException {
- if (!request(byteCount)) throw new EOFException(); //没有读到要求的数据宝宝表示不开心,后果很严重
- }
- public boolean request(long byteCount) throws IOException {
- if (byteCount < 0) throw new IllegalArgumentException(“byteCount < 0: ” + byteCount);
- if (closed) throw new IllegalStateException(“closed”);
- while (buffer.size < byteCount) {
- if (source.read(buffer, Segment.SIZE) == -1) return false; //note1
- }
- return true;
- }
public void require(long byteCount) throws IOException {
if (!request(byteCount)) throw new EOFException(); //没有读到要求的数据宝宝表示不开心,后果很严重
}
public boolean request(long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
while (buffer.size < byteCount) {
if (source.read(buffer, Segment.SIZE) == -1) return false; //note1
}
return true;
}
1、底层调用source的read方法读取目标字节数到buffer中。
到此为止我们对RealBufferedSource介绍完毕。RealBufferedSource扮演的一个中间的角色,利用source读取目标字节的字节数据,存入buffer。随后又利用buffer将结果字节流数据按照要求格式进行转换输出。
RealBufferedSink.class
接着看buffer(Sink sink)这个方法。
BufferedSink buffer()@Okio.class
- public static BufferedSink buffer(Sink sink) {
- if (sink == null) throw new IllegalArgumentException(“sink == null”);
- return new RealBufferedSink(sink);
- }
public static BufferedSink buffer(Sink sink) {
if (sink == null) throw new IllegalArgumentException("sink == null");
return new RealBufferedSink(sink);
}
跟前面一样这里创建一个RealBufferedSink对象
RealBufferedSink()@RealBufferedSink.class
- public RealBufferedSink(Sink sink) {
- this(sink, new Buffer());
- }
- public RealBufferedSink(Sink sink, Buffer buffer) {
- if (sink == null) throw new IllegalArgumentException(“sink == null”);
- this.buffer = buffer;
- this.sink = sink;
- }
public RealBufferedSink(Sink sink) {
this(sink, new Buffer());
}
public RealBufferedSink(Sink sink, Buffer buffer) {
if (sink == null) throw new IllegalArgumentException("sink == null");
this.buffer = buffer;
this.sink = sink;
}
RealBufferedSink对象的创建也同样很简单,我们直接看看write方法如何实现
write()@RealBufferedSink.class
- public BufferedSink write(byte[] source) throws IOException {
- if (closed) throw new IllegalStateException(“closed”);
- buffer.write(source);//note1
- return emitCompleteSegments();//note2
- }
public BufferedSink write(byte[] source) throws IOException {
if (closed) throw new IllegalStateException("closed");
buffer.write(source);//note1
return emitCompleteSegments();//note2
}
1、将数据写入buffer中
2、下面我们介绍其emitCompleteSegments()方法
emitCompleteSegments()@@RealBufferedSink.class
- public BufferedSink emitCompleteSegments() throws IOException {
- if (closed) throw new IllegalStateException(“closed”);
- long byteCount = buffer.completeSegmentByteCount(); //note1
- if (byteCount > 0) sink.write(buffer, byteCount);
- return this;
- }
public BufferedSink emitCompleteSegments() throws IOException {
if (closed) throw new IllegalStateException("closed");
long byteCount = buffer.completeSegmentByteCount(); //note1
if (byteCount > 0) sink.write(buffer, byteCount);
return this;
}
1、该方法我们前面没有介绍,不过它的结果就是buffer的size减去双向链表存储数据尾结点的可读数据大小,即除去尾结点的所有数据都写入到OutputStream流中。这样做的好处就是没必要写入几个字节就直接通过OutputStream写入,这样频繁的io会消耗的资源比较多。因为一个Segment大小为2048因此正常情况等到写数据大于2048时才会想OutputStream流中写入数据。这也就是BufferedSink的缓存机制,提高I/O读写效率的方法。
接着我们看下writeInt方法
writeInt()@RealBufferedSink.class
- public BufferedSink writeInt(int i) throws IOException {
- if (closed) throw new IllegalStateException(“closed”);
- buffer.writeInt(i); //note1
- return emitCompleteSegments(); //note2
- }
public BufferedSink writeInt(int i) throws IOException {
if (closed) throw new IllegalStateException("closed");
buffer.writeInt(i); //note1
return emitCompleteSegments(); //note2
}
1、与RealBufferedSource很类似,这里也是通过buffer来对目标数据进行格式的转换
2、emitCompleteSegments()方法前面已经介绍过
最后我们介绍一个RealBufferedSink的flush方法
flush()@@RealBufferedSink.class
- public void flush() throws IOException {
- if (closed) throw new IllegalStateException(“closed”);
- if (buffer.size > 0) {
- sink.write(buffer, buffer.size);
- }
- sink.flush();
- }
public void flush() throws IOException {
if (closed) throw new IllegalStateException("closed");
if (buffer.size > 0) {
sink.write(buffer, buffer.size);
}
sink.flush();
}
方法很简单就是将buffer中所有数据写入到OutputStream流中,然后调用sink的flush方法。
到此为止我们对okio的介绍就结束了,回顾一下我们学习的内容。
首先okio的存储数据的类型ByteString,Buffer各自都维护一个byte[]数组,提供了一系列方法实现了字节序列和目标格式之间的转换。Buffer并不直接对byte[]进行操作,而是操作管理Segment对象,Buffer中有一个由多个Segment组成的双向链表,同时okio还提供了一个SegmentPool用于对废弃Segment的管理,不用的Segment并不直接丢弃而是丢入这个池中,提高了Segment的使用率。Okio中Segment的创建都是通过SegmentPool来获取的。
okio提供的输入输出流分别为Sink Source,分别定义了write和read方法,实现对字节流的输入输出。不过客户使用一般都是对BufferedSource和BufferedSink进行操作,这两个接口定义了大量的方法用于对字节数据的编解码,同时通过该对象能有效提高I/O使用效率。
okio中的BufferedSink和BufferedSource这两个接口很重要,okio的输入输出流分别实现了这两个接口,而且buffer也同样实现这两个接口,因此buffer的方法和okio的输入输出流有很多方法都是相同的,这一点在实际使用过程中带来了极大的便利。
本篇是整个okhttp系列博客的完结篇。回顾一下该博客系列的内容,《OkHttp深入学习(一)——初探》对okhttp开源项目的使用方式以及经常用到的几个类进行简单学习,随后《OkHttp深入学习(二)——网络》对okhttp的网络访问底层如何实现进行分析,接着《OkHttp深入学习(三)——Cache》对okhttp的网络缓存底层如何实现进行解析,最后本篇《OkHttp深入学习(四)——0kio》对okhttp的okio子项目如何提高I/O读写效率与使用方式进行深入的分析和介绍。感谢各位的阅读!