Okio源码分析

et4248 7年前
   <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>