OKHttp的网络IO是通过OKio组件封装实现,通过OKio组件封装了读写操作,下图是关于OKio的整体框架,可以看到Sink和Source相当于jdk中的的InputStream和outputStream。我们来看下这个okio内存池的整个框架设计。
我们从一个简单的文件读写操作的例子开始,就很容易知道这两个接口是怎么用的了。
File file;
BufferedSink bufferSink;
file = new File ("./test_okio.txt");
if (!file.exists()) {
file.createNewFile();
}
//输入
bufferSink = Okio.buffer(Okio.sink(file));
bufferSink.writeString("are you ok? \n", Charset.forName("utf-8"));
bufferSink.close();
//输出
BufferedSource bufferedSource = Okio.buffer(Okio.source(file));
System.out.println(bufferedSource.readByteString().string(Charset.forName("utf-8")));
可以了解到Sink和Source是OKio是最基本的接口,定义了写入数据到缓冲区或直接读取缓冲区数据等操作。
public interface Sink extends Closeable, Flushable {
//通过缓冲区写入数据
void write(Buffer source, long byteCount) throws IOException;
//刷新 (缓冲区)
@Override void flush() throws IOException;
//超时机制
Timeout timeout();
//关闭写操作
@Override void close() throws IOException;
}
public interface Source extends Closeable {
//通过缓冲区
long read(Buffer sink, long byteCount) throws IOException;
Timeout timeout();
//关闭读操作
@Override void close() throws IOException;
}
BufferedSink作为输入数据的扩展接口,定义了非常多关于不同数据格式写入的操作接口。
public interface BufferedSink extends Sink {
Buffer buffer();
BufferedSink write(ByteString byteString) throws IOException;
BufferedSink write(byte[] source) throws IOException;
BufferedSink write(byte[] source, int offset, int byteCount) throws IOException;
long writeAll(Source source) throws IOException;
BufferedSink write(Source source, long byteCount) throws IOException;
BufferedSink writeUtf8(String string) throws IOException;
BufferedSink writeUtf8(String string, int beginIndex, int endIndex) throws IOException;
BufferedSink writeUtf8CodePoint(int codePoint) throws IOException;
BufferedSink writeString(String string, Charset charset) throws IOException;
BufferedSink writeString(String string, int beginIndex, int endIndex, Charset charset)
throws IOException;
BufferedSink writeByte(int b) throws IOException;
BufferedSink writeShort(int s) throws IOException;
BufferedSink writeShortLe(int s) throws IOException;
BufferedSink writeInt(int i) throws IOException;
BufferedSink writeIntLe(int i) throws IOException;
BufferedSink writeLong(long v) throws IOException;
BufferedSink writeLongLe(long v) throws IOException;
BufferedSink writeDecimalLong(long v) throws IOException;
BufferedSink writeHexadecimalUnsignedLong(long v) throws IOException;
BufferedSink emitCompleteSegments() throws IOException;
BufferedSink emit() throws IOException;
OutputStream outputStream();
}
RealBufferedSink是BufferedSink的实现类,在这个类中能看到是写入Buffer类对象的,我们越来越接近底层的缓存写入了。
final class RealBufferedSink implements BufferedSink {
//通过缓冲区把String类型的数据写入
@Override public BufferedSink writeString(String string, Charset charset) throws IOException {
if (closed) throw new IllegalStateException("closed");
buffer.writeString(string, charset);
return emitCompleteSegments();
}
//完成写入
@Override public BufferedSink emitCompleteSegments() throws IOException {
if (closed) throw new IllegalStateException("closed");
long byteCount = buffer.completeSegmentByteCount();
if (byteCount > 0) sink.write(buffer, byteCount);
return this;
}
}
数据量可能很大,会写入到多个Buffer中。其中Buffer中依赖的是Segment-内存块来实现类来实现数据的暂时存放的。
public final class Buffer implements BufferedSource, BufferedSink, Cloneable {
..........
//offset:写入数据的数组下标起点,
//byteCount :写入数据的长度
@Override
public Buffer write(byte[] source, int offset, int byteCount) {
int limit = offset + byteCount;
//开始循环写入数据
while (offset < limit) {
Segment tail = writableSegment(1);
// limit - offset是代写入的数据的长度
// Segment.SIZE - tail.limit是这个容器剩余空间的长度
int toCopy = Math.min(limit - offset, Segment.SIZE - tail.limit);
//调用Java方法把数据复制到容器中。
System.arraycopy(source, offset, tail.data, tail.limit, toCopy);
//记录相关偏移量
offset += toCopy;
tail.limit += toCopy;
}
//增加buffer的size
size += byteCount;
return this;
}
//获取一个Segment
Segment writableSegment(int minimumCapacity) {
if (minimumCapacity < 1 || minimumCapacity > Segment.SIZE) throw new IllegalArgumentException();
if (head == null) {
//假如当前Segment为空,则从Segment池中拿到一个
head = SegmentPool.take(); // Acquire a first segment.
return head.next = head.prev = head;
}
//获取当前Segment的前一个Segment
Segment tail = head.prev;
//检查这个Segment容器是否有剩余空间可供写入
if (tail.limit + minimumCapacity > Segment.SIZE || !tail.owner) {
//假如没有,则拿一个新的的Segment来代替这个(即链表的下一个)
tail = tail.push(SegmentPool.take()); // Append a new empty segment to fill up.
}
return tail;
}
}
科普一下,内存池其实可以减少内存碎片,分配内存更快,还避免内存泄露,用在非常频繁的网络IO的内存使用场景是非常合适的。可以看到内存池的设计如下所示,SegmentPool内存池由多个内存块组成,由双端链表组织起来的。其中内存池最大为64KB,内存块最大为8K最小为1K。
public final class SegmentPool {
static final long MAX_SIZE = 64 * 1024; // 64 KiB.
static Segment next;
static long byteCount;
//拿出一个内存块
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();
}
//回收一个闲置的Segment
static void recycle(Segment segment) {
if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
if (segment.shared) return;
synchronized (SegmentPool.class) {
if (byteCount + Segment.SIZE > MAX_SIZE) return;
byteCount += Segment.SIZE;
segment.next = next;
segment.pos = segment.limit = 0;
next = segment;
}
}
}
public final class Segment {
static final int SIZE = 8192;
static final int SHARE_MINIMUM = 1024;
final byte[] data;
int pos;
int limit;
boolean shared;
boolean owner;
//下一个内存块
Segment next;
//前一个内存块
Segment prev;
//从链表中移除一个segment
public Segment pop() {
Segment result = next != this ? next : null;
prev.next = next;
next.prev = prev;
next = null;
prev = null;
return result;
}
//从链表中添加一个segment
public Segment push(Segment segment) {
segment.prev = this;
segment.next = next;
next.prev = segment;
next = segment;
return segment;
}
}
至此,关于okhttp框架的主要实现原理,对拦截器链/IO模型/连接池组件/OKio组件的实现原理做了详细的讲解,虽然代码简短,但是包含了大量精巧的设计。这里建议是针对每个组件单独拿出来把玩下,更加容易理解。