httpAndroid开发Android知识

Okio源码分析(超详细)

2016-10-07  本文已影响2806人  gatsby_dhn

Okio是Square公司推出的Java IO库,也是OKHttp依赖的IO库。今天花了两个小时详细研究了下。分享给大家。

支持原创,转载请注明出处。

老规矩,先放图。

类图

Okio.png

AnonymousSource类代表数据源,内部引用了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可以获取所有数据。

如果觉得写得还不错可以关注我哦,后面会将更多笔记的内容整理成博客。
支持原创,转载请注明出处。

上一篇下一篇

猜你喜欢

热点阅读