Okio源码分析
et4248
8年前
<p>Okio是Square公司推出的Java IO库,也是OKHttp依赖的IO库。今天花了两个小时详细研究了下。分享给大家。</p> <p>老规矩,先放图。</p> <h2><strong>类图</strong></h2> <p style="text-align:center"><img src="https://simg.open-open.com/show/abbbc256579f9ccc34e4eea9428dd0bf.png"></p> <p style="text-align:center">Okio.png</p> <p>AnonymousSource类代表数据源,内部引用了InputStream。</p> <p>Buffer类保存缓存数据,有个head的成员变量,指向的是以Segment为节点的链表的头结点,Segment保存字节数组,是链表的节点类。</p> <h2><strong>使用</strong></h2> <p>假设我有一个test.txt文件,内容是 hello world ,现在我用Okio把它读出来。</p> <pre> <code class="language-java">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</code></pre> <p>Okio是对Java底层io的封装,所以底层io能做的Okio都能做。</p> <h2><strong>创建BufferedSource对象</strong></h2> <p>首先调用的是Okio.source(in),我们看下Okio.source方法</p> <pre> <code class="language-java">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 + ")"; } }; }</code></pre> <p>很简单,创建并返回一个匿名Source对象,在类图中我把这个匿名类叫做AnonymousSource。</p> <p>接着调用 Okio.buffer() ,看下源码:</p> <pre> <code class="language-java">public static BufferedSource buffer(Source source) { if (source == null) throw new IllegalArgumentException("source == null"); return new RealBufferedSource(source); }</code></pre> <p>创建一个RealBufferedSource对象,我们看下这个类</p> <pre> <code class="language-java">final class RealBufferedSource implements BufferedSource 核心成员变量 public final Buffer buffer; public final Source source; public RealBufferedSource(Source source) { this(source, new Buffer()); //创建一个Buffer对象 }</code></pre> <p>RealBufferedSource继承自BufferedSource,它有两个核心成员变量 Buffer buffe和Source source ,分别包含缓存和数据源,看下类图就明白了。到这里,再看下类图,AnonymousSource、RealBufferedSource,Buffer都已经创建好了。</p> <p>我们仔细看下Buffer类:</p> <pre> <code class="language-java">public final class Buffer implements BufferedSource, BufferedSink, Cloneable 成员变量 Segment head; //指向链表头部 long size; //字节数</code></pre> <h2><strong>调用source.readUtf8()</strong></h2> <p>这个source就是上面创建的RealBufferedSource对象,看下readUtf8方法:</p> <pre> <code class="language-java">@Override public String readUtf8() throws IOException { buffer.writeAll(source); //将数据从source读取到缓存buffer return buffer.readUtf8(); //从缓存读取数据 }</code></pre> <p>看下buffer.writeAll方法</p> <pre> <code class="language-java">@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; }</code></pre> <p>该方法从source读取数据,这个source是在Okio中创建的你们Souce对象。</p> <pre> <code class="language-java">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 + ")"; } };</code></pre> <p>好了,到这里InputStream的数据就被保存到了Buffer中的链表中了。</p> <p>接着应该调用RealBufferedSource.readUtf8()中的buffer.readUtf8()方法,看下源码:</p> <pre> <code class="language-java">@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; }</code></pre> <p>最终会从Buffer的链表中的头节点开始读取字节,如果头结点的字节数,不够我们读,接着读链表的下一个节点。如果头节点的字节够我们读,直接用当前Segment的字节数据构造String对象。好了,一次读取String的过程结束了。再去看看类图,肯定清楚了。</p> <h2><strong>总结</strong></h2> <p>调有Okio的source方法会返回一个实现了Source接口的对象,这个对象引用了InputStream,所以它就代表了数据源。</p> <p>Buffer保存从source读取的字节,真正存储字节在Segment对象中,Buffer保存着以Segment为节点的链表的头结点,所以Buffer可以获取所有数据。</p> <p> </p> <p> </p> <p> </p> <p>来自:http://www.jianshu.com/p/ccf24a63dca8</p> <p> </p>