Okio之Segment

作者: zYoung_Tang | 来源:发表于2019-09-27 16:29 被阅读0次

官方解释 Segment

  • Segment 是 buffer 的切割部分.
  • 每个 buffer 中的 Segment 都是循环链表中的节点,持有上一个 Segment 和下一个 Segment 的引用.
  • 每个缓存池中的 Segment 都是单链表中的节点.
  • Segment 底层数组可以被 buffer 和字符串共享.当一个 Segment 是被共享状态时不可以被回收,其字节数据也不可以只读不可写.
    • 唯一例外的是当 owner 为 true 时,数据区间为 [limit,SIZE] 中可以做写操作.
  • 每个数组都有一个 Segment 是持有者.
  • Positions, limits, prev, next 不可以被共享.

由于 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;
    }
}  

总结

  • Segment 是作为 buffer 用来切割数据的存在,通过循环链表的方式把分散的数据连在一起组成 buffer 中的数据.
  • Segment 中存储数据最大容量是 8kb
  • Segment 的 split() 中为了减少调用 System.arraycopy 带来的 CPU 损耗,操作字节数大于 1kb 时用共享 segment 的方式代替复制操作.
  • Segment 的 compact() 可以把两个数据占用率小于 50% 且连续的 segment 合并成一个,减少内存消耗.

相关文章

  • Okio之Segment

    简介 先看看源码中该类的简介: 大概意思是:1.缓冲区的组成单位结构 2.每一个Segment是一个双向循环链表,...

  • Okio之Segment

    官方解释 Segment Segment 是 buffer 的切割部分. 每个 buffer 中的 Segment...

  • Okio Segment

    segment中文意思是段,部分,在这里是缓存数据存放的地方,数据就存放在一个一个的segment中,一个segm...

  • OkHttp 4源码(6)— Okio源码解析

    本文基于Okio 2.4.3源码分析Okio - 官方地址Okio - GitHub代码地址 Okio 介绍 Ok...

  • Okio 源码分析

    Okio 源码分析 Okio , Java, Java IO OkIo 是 OKHTTP 中使用的 一个 IO的框...

  • Okio之RealBufferedSource

    先看一段简单的写文件代码: Okio的source(xxx)方法返回了Source对象(即相当于java IO中的...

  • Okio之SegmentPool

    同样,先看类简介: 大概是:没被使用的Segment的一个收集器。必要滴避免了GC导致的内存抖动和零填充。这个Se...

  • Okio源码分析

    https://github.com/square/okio Okio is a library that com...

  • RN 安卓老项目报错

    Could not resolve com.squareup.okio:okio:{strictly 1.13.0...

  • Android项目常用dependencies

    ### 网络类 * compile 'com.squareup.okio:okio:1.9.0' * compil...

网友评论

    本文标题:Okio之Segment

    本文链接:https://www.haomeiwen.com/subject/spkauctx.html