AndroidAndroid开发Android开发经验谈

Okio之Segment

2019-09-27  本文已影响0人  zYoung_Tang

官方解释 Segment

由于 Segment 的代码不是很多,所以直接贴代码和注释.

// Segment.java
final class Segment {
    // 定义了 segment 数据字节数最大值为 8kb
    static final int SIZE = 8192;
  
    // SHARE_MINIMUM 是调用 split() 时根据操作字节大小区分使用共享 segment 还是使用数据复制的标准
    static final int SHARE_MINIMUM = 1024;
  
    // 数据
    final byte[] data;
  
    // 标记下一个有效数据字节的下标
    int pos;
  
    // data 可写的下一个字节的下标,也是有效数据的最后一个字节下一个字节的下标
    int limit;
  
    // 是否与其他 segment 共享同一个数组 data,如果为 true data 中 [0,limit] 都是只读不可写的
    boolean shared;
  
    // 是否 data 的所有者,即与 shared 互斥,可以对 data 进行读写
    boolean owner;
  
    // 下一个 segment
    Segment next;
  
    // 上一个 segment
    Segment prev;
  
    Segment() {
      this.data = new byte[SIZE];
      this.owner = true;
      this.shared = false;
    }
  
    Segment(byte[] data, int pos, int limit, boolean shared, boolean owner) {
      this.data = data;
      this.pos = pos;
      this.limit = limit;
      this.shared = shared;
      this.owner = owner;
    }
  
    /**
     * 返回一个新的 segment 并与当前 segment 使用同一个 data 引用
     */
    Segment sharedCopy() {
      // 设置 shared 为 true 可以防止 segment 被回收
      shared = true;
      return new Segment(data, pos, limit, true, false);
    }
  
    /** 
     * 返回一个新的 segment , data 与当前 segment.data 的克隆
     */
    Segment unsharedCopy() {
      return new Segment(data.clone(), pos, limit, false, true);
    }
  
    /**
     * 把当前 segment 从循环链表中移除,如果移除后链表为空,就返回 null
     */
    public @Nullable Segment pop() {
      // 如果当前 segment 下一个节点就是指向它自己,那么链表只有一个 segment,result 为 null,
      // 且下面两行代码的执行毫无意义.
      Segment result = next != this ? next : null;
      prev.next = next;
      next.prev = prev;
      next = null;
      prev = null;
      return result;
    }
  
    /**
     * 把 segment 添加到循环链表中,变成当前 segment 的上一个节点,并返回被添加的 segment
     */
    public Segment push(Segment segment) {
      segment.prev = this;
      segment.next = next;
      next.prev = segment;
      next = segment;
      return segment;
    }
  
    /*
     * 把当前 Segment 拆分成两部分,数据范围分别是 [pos, pos+byteCount] [pos+byteCount, limit]
     */
    public Segment split(int byteCount) {
      // byteCount 不可以 <= 0 || byteCount 必须大于有效数据的大小,不然没必要拆分
      if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
      Segment prefix;
  
      // 两个性能指标:
      // - 避免复制操作.可以使用共享 segment 达到目的.
      // - 避免共享数据量小的 segment,这样会在链表中出现一串数据量小的 segment 且他们都是只读,会影响性能.
      // 为了得到平衡,只会在复制操作代价足够大的时候才使用共享 segment

      // 当 byteCount 大于 SHARE_MINIMUM 的时候使用共享 segment 操作,
      // 小于 SHARE_MINIMUM 的时候使用数据复制操作
      if (byteCount >= SHARE_MINIMUM) {
        prefix = sharedCopy();
      } else {
        // 从缓存池中获取 segment
        prefix = SegmentPool.take();
        // 把当前 segment 中区间为 [pos,byteCount] 的数据复制到 prefix.data [0,byteCOunt] 中
        System.arraycopy(data, pos, prefix.data, 0, byteCount);
      }
  
      // 设置 prefix 的 limit
      prefix.limit = prefix.pos + byteCount;
      // 当前 segment 的 pos 向后移动 byteCount
      pos += byteCount;
      // 把 prefix 添加到链表中且为当前 segment 的上一个节点
      prev.push(prefix);
      return prefix;
    }
  
    /**
     * 调用该方法,可以在当前节点与它的上一个节点有效数据字节数相加小于 Segemnt.SIZE 的时候
     * 合并成一个 segment,然后回收当前 segment.通常是由尾结点 tail 调用该方法.
     */
    public void compact() {
      if (prev == this) throw new IllegalStateException();
      // 当 prev 是只读的时候不可以合并
      if (!prev.owner) return; 
      // 操作字节数就是当前 segment 的数据大小
      int byteCount = limit - pos;
      // 计算 prev 的可写范围大小
      // 如果 prev 是被共享的 segment,它的可写范围是 [limit,SIZE],不是共享的话范围是 [0,pos],[limit,SIZE]
      int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
      // 如果 prev 可写大小小于当前 segment 数据大小,就不可以合并了
      if (byteCount > availableByteCount) return; 
      // 把当前 segment 数据写进 prev 中
      writeTo(prev, byteCount);
      // 把当前 segment 从循环链表中移除
      pop();
      // 回收当前 segment
      SegmentPool.recycle(this);
    }
  
    // 把当前 segment 数据中的前 byteCount 个字符写进另一个 segment sink 中
    public void writeTo(Segment sink, int byteCount) {
      // 如果 sink 是只读的抛异常
      if (!sink.owner) throw new IllegalArgumentException();
      // 如果忽略 sink 数据区间 [0,pos] 的大小,只拿区间 [limit,SIZE] 与 byteCount 作对比的话,
      // 大于 byteCount 的话可以直接把数据写进 sink 数据区间 [limit, limit+byteCount] 中
      // 小于 byteCount 的话要先把 sink 数据往前移动到 [0,limit-pos] 中,再把数据写进 sink 中
      if (sink.limit + byteCount > SIZE) {
        // 如果 sink 是被共享的 segment ,可以在 limit 之后即区间[0, limit] 都是只读不可写的,所以抛异常
        if (sink.shared) throw new IllegalArgumentException();
        // 如果 sink 可写空间大小小于 byteCount ,抛异常
        if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException();
        // 移动 sink 数据到 [0,limit-pos] 中
        System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos);
        // 移动后调整 sink.limit 和 sink.pos
        sink.limit -= sink.pos;
        sink.pos = 0;
      }
      
      // 把当前 segment 的数据写 byteCount 字节到 sink 中
      System.arraycopy(data, pos, sink.data, sink.limit, byteCount);
      // 调整 sink.limit 和当前 pos
      sink.limit += byteCount;
      pos += byteCount;
    }
}  

总结

上一篇 下一篇

猜你喜欢

热点阅读