square在开源社区的贡献是卓越的,这里是square在Android领域贡献的开源项目。
1. okio概念
-
okio是一个由square公司开发的开源库,它弥补了Java.io和java.nio的不足,能够更方便快速的读取、存储和处理数据。
-
okio有自己的流类型Source和Sink,对应于java.io的InputStream和OutputStream。
-
okio内部引入了ByteString和Buffer,提升了效率和性能。
-
okio引入了超时机制。
-
okio规模不大,代码精巧,是源码学习的好素材(okio-1.6.0.jar):
2. Source和Sink
Source代表输入流,Sink代表输出流,Source和Sink的实现逻辑基本相似,以Source为例,学习一下它的实现原理,首先来看一下Source的源码:
package okio;
import java.io.Closeable;
import java.io.IOException;
public interface Source extends Closeable {
/**
* 将此source输入流中的数据移动到sink中(至少1字节,至多byteCount字节)
* 返回移动的字节数,source读完为空时返回-1
*/
long read(Buffer sink, long byteCount) throws IOException;
/** 超时机制 */
Timeout timeout();
/**
* 关闭此source输入流并释放此source输入流持有的所有资源
* 关闭后的source输入流不能再进行读取
* 及时关闭source输入流
*/
@Override void close() throws IOException;
}
source相比于java.io的InputStream精简很多,它的具体功能通过装饰器模式在它的装饰类中实现,整体的认识一下Source和它的装饰器的实现关系:
GzipSource为支持gzip压缩的实现类,InflaterSource为GzipSource服务,用于压缩;ForwardingSource是一个具有委托功能的抽象类。
其中BufferedSource为实现Source支持缓冲区的子类接口,其中定义了缓冲区及多种类型的读方法,源码如下:
package okio;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
public interface BufferedSource extends Source {
Buffer buffer();
boolean exhausted() throws IOException;
void require(long byteCount) throws IOException;
boolean request(long byteCount) throws IOException;
byte readByte() throws IOException;
short readShort() throws IOException;
short readShortLe() throws IOException;
int readInt() throws IOException;
......
}
RealBufferedSource为BufferedSource的实现类,通常情况下我们对输入流的操作都是在操作RealBufferedSource,RealBufferedSource类中有两个主要参数,一个是Source对象,一个是新建的Buffer对象,而各种读方法都是通过Buffer来具体实现的,比如readByteArray方法:
@Override public byte[] readByteArray() throws IOException {
buffer.writeAll(source);
return buffer.readByteArray();
}
可见虽然这个类叫RealBufferedSource,但是实际上只是一个保存Buffer对象的一个代理实现,真正的实现都是在Buffer中实现的,而正是通过Buffer的应用,才实现了okio的高效性。
3. Buffer
Buffer是BufferedSink和BufferedSource的实现类,因此它既可以用来读数据,也可以用来写数据。在Buffer的注释中说明了okio的高效性:
-
copy数组时仅仅改变底层字节数组的所有权,而不是把数据从一块内存复制到另一块内存中
-
如ArrayList一样根据需要动态分配内存大小
-
避免了数组创建时的zero-fill,降低了GC的频率。
Buffer是通过Segment和SegmentPool来实现以上高效功能的,Segment译为片段,okio将数据也就是Buffer分割成片段,同时Segment有前置节点和后置节点,构成了一个双向循环链表,如图:
分片之间使用链表连接,片中使用数组存储,兼具读的连续性和写的可插入性,Segment中并不是随意的使用数组存储数据,其内部维护着一个固定长度的字节数组。Segment源码分析如下:
static final int SIZE = 2048;