Okio源码分析(超详细)
Okio是Square公司推出的Java IO库,也是OKHttp依赖的IO库。今天花了两个小时详细研究了下。分享给大家。
支持原创,转载请注明出处。
老规矩,先放图。
类图
Okio.pngAnonymousSource类代表数据源,内部引用了InputStream。
Buffer类保存缓存数据,有个head的成员变量,指向的是以Segment为节点的链表的头结点,Segment保存字节数组,是链表的节点类。
使用
假设我有一个test.txt文件,内容是hello world
,现在我用Okio把它读出来。
public static void main(String[] args) {
File file = new File("test.txt");
try {
readString(new FileInputStream(file));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void readString(InputStream in) throws IOException {
BufferedSource source = Okio.buffer(Okio.source(in)); //创建BufferedSource
String s = source.readUtf8(); //以UTF-8读
System.out.println(s); //打印
pngSource.close();
}
--------------------------------------输出-----------------------------------------
hello world
Okio是对Java底层io的封装,所以底层io能做的Okio都能做。
创建BufferedSource对象
首先调用的是Okio.source(in),我们看下Okio.source方法
public static Source source(final InputStream in) {
return source(in, new Timeout());
}
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() { //创建一个匿名Source对象,在类图中我把它叫做AnonymousSource
@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();
Segment tail = sink.writableSegment(1);
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
if (bytesRead == -1) return -1;
tail.limit += bytesRead;
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 + ")";
}
};
}
很简单,创建并返回一个匿名Source对象,在类图中我把这个匿名类叫做AnonymousSource。
接着调用Okio.buffer()
,看下源码:
public static BufferedSource buffer(Source source) {
if (source == null) throw new IllegalArgumentException("source == null");
return new RealBufferedSource(source);
}
创建一个RealBufferedSource对象,我们看下这个类
final class RealBufferedSource implements BufferedSource
核心成员变量
public final Buffer buffer;
public final Source source;
public RealBufferedSource(Source source) {
this(source, new Buffer()); //创建一个Buffer对象
}
RealBufferedSource继承自BufferedSource,它有两个核心成员变量Buffer buffe和Source source
,分别包含缓存和数据源,看下类图就明白了。到这里,再看下类图,AnonymousSource、RealBufferedSource,Buffer都已经创建好了。
我们仔细看下Buffer类:
public final class Buffer implements BufferedSource, BufferedSink, Cloneable
成员变量
Segment head; //指向链表头部
long size; //字节数
调用source.readUtf8()
这个source就是上面创建的RealBufferedSource对象,看下readUtf8方法:
@Override public String readUtf8() throws IOException {
buffer.writeAll(source); //将数据从source读取到缓存buffer
return buffer.readUtf8(); //从缓存读取数据
}
看下buffer.writeAll方法
@Override public long writeAll(Source source) throws IOException {
if (source == null) throw new IllegalArgumentException("source == null");
long totalBytesRead = 0;
for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) { //从source读取数据
totalBytesRead += readCount;
}
return totalBytesRead;
}
该方法从source读取数据,这个source是在Okio中创建的你们Souce对象。
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();
Segment tail = sink.writableSegment(1); //从buffer获取尾节点
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
int bytesRead = in.read(tail.data, tail.limit, maxToCopy); //从InputStream读取数据到Buffer中的链表的尾节点
if (bytesRead == -1) return -1;
tail.limit += bytesRead;
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 + ")";
}
};
好了,到这里InputStream的数据就被保存到了Buffer中的链表中了。
接着应该调用RealBufferedSource.readUtf8()中的buffer.readUtf8()方法,看下源码:
@Override public String readUtf8() {
try {
return readString(size, Util.UTF_8);
} catch (EOFException e) {
throw new AssertionError(e);
}
}
@Override public String readString(Charset charset) {
try {
return readString(size, charset);
} catch (EOFException e) {
throw new AssertionError(e);
}
}
@Override public String readString(long byteCount, Charset charset) throws EOFException {
checkOffsetAndCount(size, 0, byteCount);
if (charset == null) throw new IllegalArgumentException("charset == null");
if (byteCount > Integer.MAX_VALUE) {
throw new IllegalArgumentException("byteCount > Integer.MAX_VALUE: " + byteCount);
}
if (byteCount == 0) return "";
Segment s = head; //链表的头结点
if (s.pos + byteCount > s.limit) { //如果头结点的字节数,不够我们读,接着读链表的下一个节点
// If the string spans multiple segments, delegate to readBytes().
return new String(readByteArray(byteCount), charset);
}
String result = new String(s.data, s.pos, (int) byteCount, charset);//头节点的字节够我们读,直接用当前Segment的字节数据构造String对象
s.pos += byteCount;
size -= byteCount;
if (s.pos == s.limit) {
head = s.pop();
SegmentPool.recycle(s);
}
return result;
}
最终会从Buffer的链表中的头节点开始读取字节,如果头结点的字节数,不够我们读,接着读链表的下一个节点。如果头节点的字节够我们读,直接用当前Segment的字节数据构造String对象。好了,一次读取String的过程结束了。再去看看类图,肯定清楚了。
总结
调有Okio的source方法会返回一个实现了Source接口的对象,这个对象引用了InputStream,所以它就代表了数据源。
Buffer保存从source读取的字节,真正存储字节在Segment对象中,Buffer保存着以Segment为节点的链表的头结点,所以Buffer可以获取所有数据。
如果觉得写得还不错可以关注我哦,后面会将更多笔记的内容整理成博客。
支持原创,转载请注明出处。