网络编程-Okio库源码分析

797 阅读19分钟

1、简介

由square公司开发的用于IO读取,是对java 标准io流的补充封装,并自带缓冲、超时机制;使用起来方便快捷;不过想要用的效率比较高,还是需要对Okio有一定体悟的

2、脉络

下面是我自己绘制的原理图,其中超时机制并没有显示出来、socket流处理也没有画出来(增加后台线程监视包装);read、write方法操作的箭头指示传入参数;其它箭头表示流方向

原理图

整体来说,使用了包装模式或者说装饰模式来处理

  1. 通过source来包装输入,sink包装输出
  2. 具体的数据流向头尾还是java的标准IO
  3. 通过不同类型的包装,来达到不同目标,比如超时包装、buffer包装
  4. 不需要输入输出双方都包装成RealBufferXxx类,这时可以减少操作,处理更快

默认使用了Timeout超时处理,这个处理默认并不做任何动作;buffer包装提供了数据流的搬运工和池机制,增加了复用;

下面就会一些具体的方面来进行分析,然后分析IO流的流向流程,包括

  1. ByteString类
  2. 超时机制Timeout类
  3. 数据类Segment以及池、Buffer
  4. Source、Sink
  5. IO完整流向源码分析;

3、ByteString

对byte数组到string的转换进行了延迟处理,并且封装了一些常用的方法

成员变量

  final byte[] data;
  transient int hashCode;
  transient String utf8;

存储了utf-8类型的字符串和其对应的byte数组,hashcode是根据byte数组来计算的

方法 方法太多,而且基本不复杂,就不每个都介绍了

  • Base64编解码方法
  • 静态方法of,生成实例
  • 加密方法
  • 16机制字符串--byte数组转换方法
  • 还有一些字符串或者byte数组的常用操作方法

有点复杂的SegmentedByteString

继承ByteString类

  final transient byte[][] segments;
  final transient int[] directory;

其内部其实是存储了一个二维的数组,这个二维数据并不是所有数据有效,但是有效数据是连续的;父类data是这个二维数据一个有效整合,string对应的转换 之所以有意思,是这个数据经过了一道转换,而且数据采用java nio中Buffer(不是Okio的Buffer类)的思想;有两个关键方法

构造器

SegmentedByteString(Buffer buffer, int byteCount) {
    super(null);
    checkOffsetAndCount(buffer.size, 0, byteCount);
    
    int offset = 0;
    int segmentCount = 0;
    for (Segment s = buffer.head; offset < byteCount; s = s.next) {
      if (s.limit == s.pos) {
        throw new AssertionError("s.limit == s.pos");
      }
      offset += s.limit - s.pos;
      segmentCount++;
    }

    this.segments = new byte[segmentCount][];
    this.directory = new int[segmentCount * 2];
    offset = 0;
    segmentCount = 0;
    for (Segment s = buffer.head; offset < byteCount; s = s.next) {
      segments[segmentCount] = s.data;
      offset += s.limit - s.pos;
      if (offset > byteCount) {
        offset = byteCount; 
      }
      directory[segmentCount] = offset;
      directory[segmentCount + segments.length] = s.pos;
      s.shared = true;
      segmentCount++;
    }
  }

segments:是存储多个Segment的byte数组数据;Segment中数据是byte数组; directory:大小是segments的大小两倍;加入segments的大小为n,那么0 - n-1对应segments相应索引之前的有效数据累加之和,n-1 -- 2*n -1 代表数据索引对应数据有效起始位置

很明显是为了Segment和String转换接轨的

4、超时机制

涉及类有Timeout、AsyncTimeout、ForwardingTimeout、PushableTimeout

基类Timeout

变量

public static final Timeout NONE = new Timeout() {
    @Override public Timeout timeout(long timeout, TimeUnit unit) {
      return this;
    }

    @Override public Timeout deadlineNanoTime(long deadlineNanoTime) {
      return this;
    }

    @Override public void throwIfReached() throws IOException {
    }
  };

  private boolean hasDeadline;
  private long deadlineNanoTime;
  private long timeoutNanos;

静态常量NONE:什么也不做

内部成员:定义等待的时间

hasDeadline,是否存在失效截至时间

deadlineNanoTime,失效截至时间,单位为纳秒

timeoutNanos,超时时长,单位为纳秒

方法

timeoutNanos的设置和获取

public Timeout timeout(long timeout, TimeUnit unit) {
    if (timeout < 0) throw new IllegalArgumentException("timeout < 0: " + timeout);
    if (unit == null) throw new IllegalArgumentException("unit == null");
    this.timeoutNanos = unit.toNanos(timeout);
    return this;
  }

  public long timeoutNanos() {
    return timeoutNanos;
  }

deadlineNanoTime、timeoutNanos的设置和获取

  public boolean hasDeadline() {
    return hasDeadline;
  }

  public long deadlineNanoTime() {
    if (!hasDeadline) throw new IllegalStateException("No deadline");
    return deadlineNanoTime;
  }

  public Timeout deadlineNanoTime(long deadlineNanoTime) {
    this.hasDeadline = true;
    this.deadlineNanoTime = deadlineNanoTime;
    return this;
  }
  
  public final Timeout deadline(long duration, TimeUnit unit) {
    if (duration <= 0) throw new IllegalArgumentException("duration <= 0: " + duration);
    if (unit == null) throw new IllegalArgumentException("unit == null");
    return deadlineNanoTime(System.nanoTime() + unit.toNanos(duration));
  }

清除超时信息

public Timeout clearTimeout() {
    this.timeoutNanos = 0;
    return this;
  }

  public Timeout clearDeadline() {
    this.hasDeadline = false;
    return this;
  }

超时时,转化为自定义IO异常

public void throwIfReached() throws IOException {
    if (Thread.interrupted()) {
      Thread.currentThread().interrupt(); // Retain interrupted status.
      throw new InterruptedIOException("interrupted");
    }

    if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {
      throw new InterruptedIOException("deadline reached");
    }
  }

获取不为0的,最小值

  static long minTimeout(long aNanos, long bNanos) {
    if (aNanos == 0L) return bNanos;
    if (bNanos == 0L) return aNanos;
    if (aNanos < bNanos) return aNanos;
    return bNanos;
  }

利用Synchronized关键字锁,使用Object对象进行挂起、唤醒操作

public final void waitUntilNotified(Object monitor) throws InterruptedIOException {
    try {
      boolean hasDeadline = hasDeadline();
      long timeoutNanos = timeoutNanos();

      if (!hasDeadline && timeoutNanos == 0L) {
        monitor.wait();
        return;
      }

      long waitNanos;
      long start = System.nanoTime();
      if (hasDeadline && timeoutNanos != 0) {
        long deadlineNanos = deadlineNanoTime() - start;
        waitNanos = Math.min(timeoutNanos, deadlineNanos);
      } else if (hasDeadline) {
        waitNanos = deadlineNanoTime() - start;
      } else {
        waitNanos = timeoutNanos;
      }

      long elapsedNanos = 0L;
      if (waitNanos > 0L) {
        long waitMillis = waitNanos / 1000000L;
        monitor.wait(waitMillis, (int) (waitNanos - waitMillis * 1000000L));
        elapsedNanos = System.nanoTime() - start;
      }

      if (elapsedNanos >= waitNanos) {
        throw new InterruptedIOException("timeout");
      }
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new InterruptedIOException("interrupted");
    }
  }

代码流程大致:

  1. 如果没有超时截至时间,且等待时间为0,则挂起,等待唤醒(默认构造,就满足此条件)
  2. 计算等待时间,若等待时间大于0,则进行等待此时间
  3. 剩余时间大于等待时间,则打断当前线程,并抛出自定义io异常

ForwardingTimeout

继承TimeOut类,使用了静态代理模式,算是一个代理类

PushableTimeout

继承TimeOut类,增加了操作,并没有改变基类的操作;而是提供了对Timeout对象出、入操作

  private Timeout pushed;
  private boolean originalHasDeadline;
  private long originalDeadlineNanoTime;
  private long originalTimeoutNanos;

辅助变量,和父类变量含义一一对应,只不过它记录的是pushed变量的原有值,辅助pushed的出、入

入操作:记录入Timeout对象原始数据,并以当前Timeout的数据来修改入Timeout的截止时间、等待时间

void push(Timeout pushed) {
    this.pushed = pushed;
    this.originalHasDeadline = pushed.hasDeadline();
    this.originalDeadlineNanoTime = originalHasDeadline ? pushed.deadlineNanoTime() : -1L;
    this.originalTimeoutNanos = pushed.timeoutNanos();

    pushed.timeout(minTimeout(originalTimeoutNanos, timeoutNanos()), TimeUnit.NANOSECONDS);

    if (originalHasDeadline && hasDeadline()) {
      pushed.deadlineNanoTime(Math.min(deadlineNanoTime(), originalDeadlineNanoTime));
    } else if (hasDeadline()) {
      pushed.deadlineNanoTime(deadlineNanoTime());
    }
  }

出操作,复原pushed变量值


  void pop() {
    pushed.timeout(originalTimeoutNanos, TimeUnit.NANOSECONDS);

    if (originalHasDeadline) {
      pushed.deadlineNanoTime(originalDeadlineNanoTime);
    } else {
      pushed.clearDeadline();
    }
  }

AsyncTimeout

继承Timeout类,内部增加了单链表,并对单链表进行监听 特点: 1、单链表,以截至时间从小到大排序 2、使用后台守护线程,检测队列中执行时长,超时的调用timeout方法,这个方法重写,以通知写或者读操作,停止处理

变量

head 链表头,next下个节点

  static @Nullable AsyncTimeout head;

  private boolean inQueue;
  
  private @Nullable AsyncTimeout next;

  private long timeoutAt;

inQueue,是否排队,如果排队timeouAt表示截至时间(这个是根据几类中等待时长和截止时间一起来得到的)

节点进入排队监视

public final void enter() {
    if (inQueue) throw new IllegalStateException("Unbalanced enter/exit");
    long timeoutNanos = timeoutNanos();
    boolean hasDeadline = hasDeadline();
    if (timeoutNanos == 0 && !hasDeadline) {
      return;
    }
    inQueue = true;
    scheduleTimeout(this, timeoutNanos, hasDeadline);
  }

  private static synchronized void scheduleTimeout(AsyncTimeout node, long timeoutNanos, boolean hasDeadline) {

    if (head == null) {
      head = new AsyncTimeout();
      new Watchdog().start();
    }

    long now = System.nanoTime();
    if (timeoutNanos != 0 && hasDeadline) {

      node.timeoutAt = now + Math.min(timeoutNanos, node.deadlineNanoTime() - now);
    } else if (timeoutNanos != 0) {
      node.timeoutAt = now + timeoutNanos;
    } else if (hasDeadline) {
      node.timeoutAt = node.deadlineNanoTime();
    } else {
      throw new AssertionError();
    }

    long remainingNanos = node.remainingNanos(now);
    for (AsyncTimeout prev = head; true; prev = prev.next) {
      if (prev.next == null || remainingNanos < prev.next.remainingNanos(now)) {
        node.next = prev.next;
        prev.next = node;
        if (prev == head) {
          AsyncTimeout.class.notify();
        }
        break;
      }
    }
  }

大致流程如下:

  1. 如果没有截止时间且等待时长为0,则不需要被监视,直接退出,表示不用被后台监视,不会主动去阻止操作
  2. 进行排队标志置true,并进行排队操作
  3. 如果当且队列为空,则启用一个不需要等待的头节点,并开启后台线程监视
  4. 计算截止时间,保存在timeoutAt变量中
  5. 根据timeoutAt值,进行入队,如果入队时,只有头数据,则进行唤醒操作(在后台线程中,防止头节点被删除)

节点监视

监视线程是一个守护线程;

  • 如果仅剩下头节点,则头节点置空,结束
  • 如果通过awaitTimeout获取的节点为空,则继续处理;节点不为空,则执行timeout操作

也就是timeout的动作是超时执行的关键;通过异常机制,来阻止读写操作的

private static final class Watchdog extends Thread {
    Watchdog() {
      super("Okio Watchdog");
      setDaemon(true);
    }

    public void run() {
      while (true) {
        try {
          AsyncTimeout timedOut;
          synchronized (AsyncTimeout.class) {
            timedOut = awaitTimeout();
            if (timedOut == null) continue;
            if (timedOut == head) {
              head = null;
              return;
            }
          }

          timedOut.timedOut();
        } catch (InterruptedException ignored) {
        }
      }
    }
  }

awaitTimeout方法 和后台监听线程配合执行;整体来说,非头节点,等待时间过后执行timeout操作,队列中无数据1ms秒则关闭监听线程

static @Nullable AsyncTimeout awaitTimeout() throws InterruptedException {
    AsyncTimeout node = head.next;
    if (node == null) {
      long startNanos = System.nanoTime();
      AsyncTimeout.class.wait(IDLE_TIMEOUT_MILLIS);
      return head.next == null && (System.nanoTime() - startNanos) >= IDLE_TIMEOUT_NANOS ? head  : null;
    }

    long waitNanos = node.remainingNanos(System.nanoTime());

    if (waitNanos > 0) {
      long waitMillis = waitNanos / 1000000L;
      waitNanos -= (waitMillis * 1000000L);
      AsyncTimeout.class.wait(waitMillis, (int) waitNanos);
      return null;
    }

    head.next = node.next;
    node.next = null;
    return node;
  }

大致流程:

  1. 如果仅有一个头节点,则头节点等待1ms,在等待过程中被唤醒,说明又加入新节点了;没有,则返回头节点,由后台监视结束自己
  2. 如果存在数据且节点需要等待,则进行等待,等待结束后,返回null
  3. 节点从链表去除,并返回节点

exit方法 exit无参方法返回true,表示执行操作,false表示正常退出;因为在后台监视线程中,如果超时了,则已经退出队列了,这里返回结果就是以是否在队列中为标准的

public final boolean exit() {
    if (!inQueue) return false;
    inQueue = false;
    return cancelScheduledTimeout(this);
  }

  private static synchronized boolean cancelScheduledTimeout(AsyncTimeout node) {
    for (AsyncTimeout prev = head; prev != null; prev = prev.next) {
      if (prev.next == node) {
        prev.next = node.next;
        node.next = null;
        return false;
      }
    }
    return true;
  }
  
  final void exit(boolean throwOnTimeout) throws IOException {
    boolean timedOut = exit();
    if (timedOut && throwOnTimeout) throw newTimeoutException(null);
  }

  final IOException exit(IOException cause) throws IOException {
    if (!exit()) return cause;
    return newTimeoutException(cause);
  }

exit有参方法,表示抛出异常

读写操作对象生成

public final Sink sink(final Sink sink) {
    return new Sink() {
      @Override public void write(Buffer source, long byteCount) throws IOException {
        checkOffsetAndCount(source.size, 0, byteCount);

        while (byteCount > 0L) {
          long toWrite = 0L;
          for (Segment s = source.head; toWrite < TIMEOUT_WRITE_SIZE; s = s.next) {
            int segmentSize = s.limit - s.pos;
            toWrite += segmentSize;
            if (toWrite >= byteCount) {
              toWrite = byteCount;
              break;
            }
          }

          boolean throwOnTimeout = false;
          enter();
          try {
            sink.write(source, toWrite);
            byteCount -= toWrite;
            throwOnTimeout = true;
          } catch (IOException e) {
            throw exit(e);
          } finally {
            exit(throwOnTimeout);
          }
        }
      }

      @Override public void flush() throws IOException {
        boolean throwOnTimeout = false;
        enter();
        try {
          sink.flush();
          throwOnTimeout = true;
        } catch (IOException e) {
          throw exit(e);
        } finally {
          exit(throwOnTimeout);
        }
      }

      @Override public void close() throws IOException {
        boolean throwOnTimeout = false;
        enter();
        try {
          sink.close();
          throwOnTimeout = true;
        } catch (IOException e) {
          throw exit(e);
        } finally {
          exit(throwOnTimeout);
        }
      }

      @Override public Timeout timeout() {
        return AsyncTimeout.this;
      }

      @Override public String toString() {
        return "AsyncTimeout.sink(" + sink + ")";
      }
    };
  }

  public final Source source(final Source source) {
    return new Source() {
      @Override public long read(Buffer sink, long byteCount) throws IOException {
        boolean throwOnTimeout = false;
        enter();
        try {
          long result = source.read(sink, byteCount);
          throwOnTimeout = true;
          return result;
        } catch (IOException e) {
          throw exit(e);
        } finally {
          exit(throwOnTimeout);
        }
      }

      @Override public void close() throws IOException {
        boolean throwOnTimeout = false;
        enter();
        try {
          source.close();
          throwOnTimeout = true;
        } catch (IOException e) {
          throw exit(e);
        } finally {
          exit(throwOnTimeout);
        }
      }

      @Override public Timeout timeout() {
        return AsyncTimeout.this;
      }

      @Override public String toString() {
        return "AsyncTimeout.source(" + source + ")";
      }
    };
  }

在sink、source的读写操作,使用try catch,来捕获timeout方法带来的异常,进而停止读写操作

5、核心数据类

Segment是核心数据类,是一个双链表结构的数据类,并且采用了池技术

5.1 Segment类

一个首位相连的双向链表;但是我在这个类中没有找到首尾相连的证据,但是处理过程都是按照首尾相连来处理的

成员变量

  static final int SIZE = 8192;
  static final int SHARE_MINIMUM = 1024;

  final byte[] data;
  int pos;
  int limit;
  boolean shared;
  boolean owner;
  Segment next;
  Segment prev;

变量含义如下:

  • SIZE 数据存储大小
  • SHARE_MINIMUM深copy、浅copy大小分界;大于此值,浅copy也就是共享数据
  • data ,pos, limit 数据,有效数据起始位置,和结束位置
  • next、prev 链表节点
  • shared、owner 表明data数据对象是否当前对象独占,还是和其它数据共享
  final Segment sharedCopy() {
    shared = true;
    return new Segment(data, pos, limit, true, false);
  }

  final Segment unsharedCopy() {
    return new Segment(data.clone(), pos, limit, false, true);
  }

共享生成新对象,为浅copy;独占生成对象为深copy

  public final @Nullable Segment pop() {
    Segment result = next != this ? next : null;
    prev.next = next;
    next.prev = prev;
    next = null;
    prev = null;
    return result;
  }

  public final Segment push(Segment segment) {
    segment.prev = this;
    segment.next = next;
    next.prev = segment;
    next = segment;
    return segment;
  }

pop出队操作;push入队操作,当前对象后面入队

分割方法

public final Segment split(int byteCount) {
    if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
    Segment prefix;
    if (byteCount >= SHARE_MINIMUM) {
      prefix = sharedCopy();
    } else {
      prefix = SegmentPool.take();
      System.arraycopy(data, pos, prefix.data, 0, byteCount);
    }

    prefix.limit = prefix.pos + byteCount;
    pos += byteCount;
    prev.push(prefix);
    return prefix;
  }
  1. 分割出去的对象数据大小,必须大于0 且小于当前有效数据大小
  2. 分割大小大于 SHARE_MINIMUM时,生成共享对象
  3. 独占对象依赖池技术获取,并且深copy数据
  4. 对分割后的两个对象进行有效数据位置处理
  5. 把分割出的数据放在当前数据前面入队

拼接方法

  public final void compact() {
    if (prev == this) throw new IllegalStateException();
    if (!prev.owner) return; 
    int byteCount = limit - pos;
    int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
    if (byteCount > availableByteCount) return; 
    writeTo(prev, byteCount);
    pop();
    SegmentPool.recycle(this);
  }

  public final void writeTo(Segment sink, int byteCount) {
    if (!sink.owner) throw new IllegalArgumentException();
    if (sink.limit + byteCount > SIZE) {
      if (sink.shared) throw new IllegalArgumentException();
      if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException();
      System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos);
      sink.limit -= sink.pos;
      sink.pos = 0;
    }

    System.arraycopy(data, pos, sink.data, sink.limit, byteCount);
    sink.limit += byteCount;
    pos += byteCount;
  }
  1. 拼接数据,是和队列前一个数据拼接,并且把当前数据追加到前驱节点中,当前节点出队,并回收
  2. 执行拼接动作,必须前驱和当前节点不是同一个节点,且前驱节点不是共享节点,剩余的存储空间可以存下当前节点数据
  3. 如果拼接时,从limit开始到byte数组结尾,可以存下,直接放;不行,需要把有效数据从poscopy到0的位置

5.2 数据池 SegmentPool

一个私有构造的工具类,除了构造器就仅含有静态变量、静态方法;池中数据采用链表结构,池中Segment存在的大小最多为8个(一个Segment的容量为8kb);取出节点和回收使用了线程同步;仅仅是独占数据Segment的对象池 next:链表头节点 byteCount:池中对象可存数据大小

final class SegmentPool {

  static final long MAX_SIZE = 64 * 1024;
  static @Nullable Segment next;
  static long byteCount;
  private SegmentPool() {
  }

  static Segment take() {
    synchronized (SegmentPool.class) {
      if (next != null) {
        Segment result = next;
        next = result.next;
        result.next = null;
        byteCount -= Segment.SIZE;
        return result;
      }
    }
    return new Segment();
  }

  static void recycle(Segment segment) {
    if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
    if (segment.shared) return; 
    synchronized (SegmentPool.class) {
      if (byteCount + Segment.SIZE > MAX_SIZE) return;
      byteCount += Segment.SIZE;
      segment.next = next;
      segment.pos = segment.limit = 0;
      next = segment;
    }
  }
}

take方法:取节点;如果单链表next节点不为空,表示存在数据,把next去除,并把next的next置为next,数据大小byteCount也跟着变化;否则新建节点

recycle方法:回收节点;共享、存在前驱后继的不回收,可存数据大小超标了也不回收;回收后Segment对象数据重置,并加入回收数据链表头部

5.3 Buffer类

通过读写方法实现了,流数据的转移;也可以不这么用,我觉得这是这个架构纯正的过程;单独的读、或者写,仅仅用Buffer作为数据的存储

成员变量

  Segment head;
  long size;

Segment头节点,数据总共大小

方法太多了,这些介绍几个关键方法

writableSegment方法

    Segment 
    (int minimumCapacity) {
    if (minimumCapacity < 1 || minimumCapacity > Segment.SIZE) throw new IllegalArgumentException();

    if (head == null) {
      head = SegmentPool.take();
      return head.next = head.prev = head;
    }

    Segment tail = head.prev;
    if (tail.limit + minimumCapacity > Segment.SIZE || !tail.owner) {
      tail = tail.push(SegmentPool.take());
    }
    return tail;
  }

在封装io输入流时用到,获取Buffer中存入数据的队尾(队尾节点数据肯定不是满的);从这里可以看出,head数据链表是个头尾相连的结构

数据流读入方法

@Override public long read(Buffer sink, long byteCount) {
    if (sink == null) throw new IllegalArgumentException("sink == null");
    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
    if (size == 0) return -1L;
    if (byteCount > size) byteCount = size;
    sink.write(this, byteCount);
    return byteCount;
  }

参数sink为待写入的数据,当前Buffer对象保存inputStream中读出的数据;通过写入方法来完成实际流向

写入方法

public void write(Buffer source, long byteCount) {
    if (source == null) throw new IllegalArgumentException("source == null");
    if (source == this) throw new IllegalArgumentException("source == this");
    checkOffsetAndCount(source.size, 0, byteCount);

    while (byteCount > 0) {
      if (byteCount < (source.head.limit - source.head.pos)) {
        Segment tail = head != null ? head.prev : null;
        if (tail != null && tail.owner
            && (byteCount + tail.limit - (tail.shared ? 0 : tail.pos) <= Segment.SIZE)) {
          
          source.head.writeTo(tail, (int) byteCount);
          source.size -= byteCount;
          size += byteCount;
          return;
        } else {
          source.head = source.head.split((int) byteCount);
        }
      }

      Segment segmentToMove = source.head;
      long movedByteCount = segmentToMove.limit - segmentToMove.pos;
      source.head = segmentToMove.pop();
      if (head == null) {
        head = segmentToMove;
        head.next = head.prev = head;
      } else {
        Segment tail = head.prev;
        tail = tail.push(segmentToMove);
        tail.compact();
      }
      source.size -= movedByteCount;
      size += movedByteCount;
      byteCount -= movedByteCount;
    }
  }

source参数为存有数据流的对象,当前对象为接收数据流的对象;基本流程

  1. 检验参数的合法性;数据流必须有这么多数据
  2. 如果可以把追加数据输出Buffer的尾节点数据中,则copy数据
  3. . 不行时,把输入Buffer数据分离处一个count大小的数据节点
  4. 输入Buffer的head数据移动到输出Buffer的尾部(这里还防止了刚分割的头数据为空的多线程问题)
  5. 重新执行2-4过程

由此可以看出: 新来数据追加到尾部节点,或者由输入的Segment移动过来

写入过程可以发现,读取和写入可以不是一一对应的(从Okio类中包装和RealBufferedSource的read方法,可以看出每次从imputStream流中读入自己Buffer中数据大小一定不能超过一个Segment的大小;每次写出却按照自己Buffer的总数据和要求数据求最小值也就是可能超过每次读入的数据)

6、 Okio的输入输出

其输入流是实现了Source接口的,其输出流是实现了Sink接口的;除了临时对象,其实现的只有RealBufferedSource、RealBufferedSink类

6.1 RealBufferedSource

成员变量

  public final Buffer buffer = new Buffer();
  public final Source source;
  boolean closed;

source:包括IO输入流的对象 buffer:作为从io输入流读出数据存储的地方 closed:io输入流是否关闭

介绍下主要的几个方法

读方法

  public long read(Buffer sink, long byteCount) throws IOException {
    if (sink == null) throw new IllegalArgumentException("sink == null");
    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
    if (closed) throw new IllegalStateException("closed");

    if (buffer.size == 0) {
      long read = source.read(buffer, Segment.SIZE);
      if (read == -1) return -1;
    }

    long toRead = Math.min(byteCount, buffer.size);
    return buffer.read(sink, toRead);
  }

首先读入到自己的buffer中,然后通过buffer 读写方法进行数据转移

仅向自己的buffer中准备数据

  @Override public void require(long byteCount) throws IOException {
    if (!request(byteCount)) throw new EOFException();
  }

  @Override public boolean request(long byteCount) throws IOException {
    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
    if (closed) throw new IllegalStateException("closed");
    while (buffer.size < byteCount) {
      if (source.read(buffer, Segment.SIZE) == -1) return false;
    }
    return true;
  }

仅仅做了从IO流中读取数据并保存到buffer中,这时的buffer可以作为Sink的写入数据Buffer

还有一些读取基本类型,byte数组,utf8字符串的方法,close等方法

6.2 RealBufferedSink

成员变量

  public final Buffer buffer = new Buffer();
  public final Sink sink;
  boolean closed;

包含IO输出流的sink,存储等待写入IO流数据的buffer,流状态closed

写方法

 public void write(Buffer source, long byteCount)
      throws IOException {
    if (closed) throw new IllegalStateException("closed");
    buffer.write(source, byteCount);
    emitCompleteSegments();
  }
  
  public BufferedSink emitCompleteSegments() throws IOException {
    if (closed) throw new IllegalStateException("closed");
    long byteCount = buffer.completeSegmentByteCount();
    if (byteCount > 0) sink.write(buffer, byteCount);
    return this;
  }
  1. 数据会从待写入buffer转移自己的buffer中(感觉流程有点浪费直接写进来不就行了吗)
  2. 把本身buffer中的数据写入IO输出流中

Buffer的completeSegmentByteCount方法;计算可以写入的数据时,如果尾部的Segment数据未满,则不包括

emit方法 相对于emitCompleteSegments方法,必定把buffer中数据全部写入IO输出流

  public BufferedSink emit() throws IOException {
    if (closed) throw new IllegalStateException("closed");
    long byteCount = buffer.size();
    if (byteCount > 0) sink.write(buffer, byteCount);
    return this;
  }

flush方法

会把剩余数据全部写入IO输出流,并且清理Buffer中Segment数据,并调用IO流flush方法;

public void flush() throws IOException {
    if (closed) throw new IllegalStateException("closed");
    if (buffer.size > 0) {
      sink.write(buffer, buffer.size);
    }
    sink.flush();
  }

7、IO流向源码分析

IO流向要从Okio代码来分析:

7.1 输入流InputStream的包装


public static Source source(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() {
      @Override public long read(Buffer sink, long byteCount) throws IOException {
        if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
        if (byteCount == 0) return 0;
        try {
          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;
        } catch (AssertionError e) {
          if (isAndroidGetsocknameError(e)) throw new IOException(e);
          throw e;
        }
      }

      @Override public void close() throws IOException {
        in.close();
      }

      @Override public Timeout timeout() {
        return timeout;
      }

      @Override public String toString() {
        return "source(" + in + ")";
      }
    };
  }

流程如下:

  1. 调用timeout超时方法,如果超时就会被打断,否则不做任何处理(如果是异步超时对象相关,则可能由后台监视线程打断)
  2. 找到Buffer中Segment节点的队尾,依据队尾节点可以存入数据来决定io输入预期流读取数据量,返回实际读取数据量

这时数据可以读取到RealBufferSink的成员变量buffer中,RealBufferSink再调用写入操作即可

也可以进一步封装;通过buffer作为Sink对象参数达到同样的目的;不过其buffer要调用read方法读数据到buffer中

  public static BufferedSource buffer(Source source) {
    return new RealBufferedSource(source);
  }

7.2 IO输出流包装

public static BufferedSink buffer(Sink sink) {
    return new RealBufferedSink(sink);
  }

  public static Sink sink(OutputStream out) {
    return sink(out, new Timeout());
  }

  private static Sink sink(final OutputStream out, final Timeout timeout) {
    if (out == null) throw new IllegalArgumentException("out == null");
    if (timeout == null) throw new IllegalArgumentException("timeout == null");

    return new Sink() {
      @Override public void write(Buffer source, long byteCount) throws IOException {
        checkOffsetAndCount(source.size, 0, byteCount);
        while (byteCount > 0) {
          timeout.throwIfReached();
          Segment head = source.head;
          int toCopy = (int) Math.min(byteCount, head.limit - head.pos);
          out.write(head.data, head.pos, toCopy);

          head.pos += toCopy;
          byteCount -= toCopy;
          source.size -= toCopy;

          if (head.pos == head.limit) {
            source.head = head.pop();
            SegmentPool.recycle(head);
          }
        }
      }

      @Override public void flush() throws IOException {
        out.flush();
      }

      @Override public void close() throws IOException {
        out.close();
      }

      @Override public Timeout timeout() {
        return timeout;
      }

      @Override public String toString() {
        return "sink(" + out + ")";
      }
    };
  }

流程大致如下:

  1. 超时检测
  2. 写入IO流数据,每次最大为Segment的大小,从Buffer的头Segment开始
  3. 如果Buffer中Segment中数据写完,则回收
  4. 重复执行2-3,知道写入到预期数据

7.3 Socket的包装


  public static Sink sink(Socket socket) throws IOException {
    if (socket == null) throw new IllegalArgumentException("socket == null");
    if (socket.getOutputStream() == null) throw new IOException("socket's output stream == null");
    AsyncTimeout timeout = timeout(socket);
    Sink sink = sink(socket.getOutputStream(), timeout);
    return timeout.sink(sink);
  }

    public static Source source(Socket socket) throws IOException {
    if (socket == null) throw new IllegalArgumentException("socket == null");
    if (socket.getInputStream() == null) throw new IOException("socket's input stream == null");
    AsyncTimeout timeout = timeout(socket);
    Source source = source(socket.getInputStream(), timeout);
    return timeout.source(source);
  }

比普通的IO流操作多了:timeout的进一步包装和AsyncTimeout的timeout方法(AsyncTimeout中空实现)实现;

实现timeout方法:关闭流,当再进行操作时会出现异常

进一步包装:增加后台超时监视,这时会调用timeout方法

8、原理小结

  1. io流到Buffer的数据转移,每次必定少于1KB;buffer到buffer的转移,大小不限制
  2. 普通io仅仅对读写过程中做了超时检测停止操作;而socket网络io增加了后台线程超时监视,主要解决网络两端传递超时
  3. RealBufferedXxx,这两个流类,实现了缓存数据存储在Buffer中,而且IO读取至少是Segment.SIZE的大小(暂时为8KB)
  4. RealBufferedSource的流到RealBufferedSink的流动,每一次流动操作低于Segment.SIZE,拆分移动;应尽量减少拆分
  5. 每次读写到Buffer中时,流操作个数随意时,会存在拆分重组
  6. 类中还有一些细节这里并没有写出,有兴趣的同学可以细品,有利于我们更高效的使用

由于看第三库源码,那么对每个类的认知讲解就不会那么详细,但这些细节需要自己去消化,如果没有消化,你对整体的脉络肯定疑问重重;如果有什么问题,欢迎大家留言讨论

技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!