Okio源码分析(超详细)

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可以获取所有数据。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容