okio中数据存储的基本单位Segment

   1、Segment是Buffer缓冲区存储数据的基本单位,每个Segment能存储的最大字节是8192也就是8k的数据
     /** The size of all segments in bytes. */
     static final int SIZE = 8192;
     
   2、SHARE_MINIMUM是用来共享的阈值,超过1k的数据才可以进行共享
     /** Segments will be shared when doing so avoids {@code arraycopy()} of this many bytes. */
      static final int SHARE_MINIMUM = 1024;
    
   3、Segment存储的数据是字节数组data
      final byte[] data;
      
   4、Segmen中有记录可读的位置的pos,每次读取数据的时候从这个下标开始
      /** The next byte of application data byte to read in this segment. */
       int pos;
   
   5、Segmen中有记录可写的位置的limit,每次写数据的时候从这个下标开始  
      /** The first byte of available data ready to be written to. */
      int limit;
  
   6、boolean shared; 标识是否本Segment可以共享,实际就是浅拷贝,共享的是里面的data数据
  
   7、boolean owner; 标识data是否是本Segment对象所拥有的的,副本的Segment对象只能读
   
   8、因为Buffer数据的存储Segment的设计是采用双向链表的结构,因此就出现了Segment next;和 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;
  }
  
  //浅拷贝一个Segmen对象,同时标记当前的Segment对象是可共享的、新的Segment对象owner是false
  final Segment sharedCopy() {
    shared = true;
    return new Segment(data, pos, limit, true, false);
  }
  
  //深拷贝-重建Segment,data数据也是新的
  final Segment unsharedCopy() {
    return new Segment(data.clone(), pos, limit, false, true);
  }
  
  //把当前的Segment对象从链表中删除,并返回他的next,如果只有一个Segment
  //也就是next==this,直接返回null
  public final @Nullable Segment pop() {
    Segment result = next != this ? next : null;
    prev.next = next;
    next.prev = prev;
    next = null;
    prev = null;
    return result;
  }
  
 //在当前Segment对象的后面新增一个next对象,并返回这个新增的Segment
 public final Segment push(Segment segment) {
    segment.prev = this;
    segment.next = next;
    next.prev = segment;
    next = segment;
    return segment;
  }
 //把当前的Segment拆分成2个Segment对象
 public final Segment split(int byteCount) {
    //如果划分的长度是0或者超出了当前Segment的内容(我就存了3个长度东西,你让我划分长度是5,那我就报错)
    if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
    Segment prefix;
    //如果长度超过了1字节,那么我就通过sharedCopy的浅拷贝方式创建一个新的Segment对象:此时我们两个对象的数据是相同的
    //pos、limit也是相同的
    if (byteCount >= SHARE_MINIMUM) {
      prefix = sharedCopy();
    } else {
      //不超过可共享的阈值那就从池子里面去拿(典型的享元模式:可参考Handler中的Message设计模式)
      prefix = SegmentPool.take();
      //通过arraycopy的方式把data中的byteCount的字节从pos开始,拷贝到prefix.data中去,从0开始
      System.arraycopy(data, pos, prefix.data, 0, byteCount);
    }
    //通过上面的方式我们就得到了一个新的Segment对象:pos可能是0也可能是当前Segment的pos,取决你的生成方式。
    //新的Segment对象的limit就是pos+byteCount,表示可写的下标位置
    prefix.limit = prefix.pos + byteCount;
    //当前的Segment对象的pos就是pos+byteCount:因为被别人拿走了byteCount长度,limit的位置不变
    pos += byteCount;
    //把新的Segment对象放在prev的后面连起来,这样就形成了新的链接(prefix在当前Segment的前面,前Segment-Pre的后面)
    prev.push(prefix);
    //返回
    return prefix;
  //整理空间:被分割的Segment或者被读取的Segment会出现pos不为0的情况,导致空间的浪费
  public final void compact() {
    //只有自己一个Segment这里抛出了异常,个人认为没有必要,不整理不就行了,干嘛给人家抛异常
    if (prev == this) throw new IllegalStateException();
    //副本没有操作权限
    if (!prev.owner) return; // Cannot compact: prev isn't writable.
    //需要移动的长度就是当前的有效内容的长度
    int byteCount = limit - pos;
    //计算下前一个Segment可用空间
    int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
    //如果前一个Segment的剩余可用空间不足以放下当前的长度直接返回,无能为力
    if (byteCount > availableByteCount) return; // Cannot compact: not enough writable space.
    //如果前面的Segment可用剩余的空间能放得下就把byteCount长度的字节写到pre中
    writeTo(prev, byteCount);
    //写完之后自己的这个Segment就可以被弹出回收了
    pop();
    SegmentPool.recycle(this);
  }
  
只是把当前的Segment内容移动到pre中

  //把当前Segment中byteCount长度的字节移到sink中
 public final void writeTo(Segment sink, int byteCount) {
    //副本是不能进行操作的
    if (!sink.owner) throw new IllegalArgumentException();
    //如果sink当前可写的位置+需要移动的长度超过了Segment中存储的最大值(不一定会抛异常哦,因为pos可能不是0,也就是前面都空余的空间)
    if (sink.limit + byteCount > SIZE) {
      // 如果sink是共享的不能写
      if (sink.shared) throw new IllegalArgumentException();
      //如果整个Segment可写的空间都不足以放下byteCount长度,那就是真的放不下了:抛异常
      if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException();
      //虽然limt后面放不下,当时pos前面还有空间,加起来剩余的空间能放下,这时候整理下sink,进行一次移动
      //从pos位置移动sink.limit - sink.pos长度当前有效内容,移动到从0开始的位置
      System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos);
      //移动之后更新sink.limit的位置:向前挪了pos个长度
      sink.limit -= sink.pos;
      //移动之后更新sink.pos的位置:从pos向前挪了pos个位置,也就是0
      sink.pos = 0;
    }

    //整理完sink之后,或者sink.limit + byteCount能放得下byteCount个字节后,把当前Segment中的byteCount字节移动到sink中
    System.arraycopy(data, pos, sink.data, sink.limit, byteCount);
    //更新sink.limit的位置
    sink.limit += byteCount;
    //更新当前Segment的pos的位置:被拿走了了byteCount长度,所以需要后移byteCount个长度
    pos += byteCount;
  }

写入方法的总结:如果sink中limt之后的剩余空间能够写的下byteCount的长度直接通过arraycopy把data中的数据移动到sink的limt后面即可,然后更新sink的limit和当前Segment的pos

如果sink中limt之后的空间放不下byteCount长度der字节,就会判断sink中可用空间是否能放下,如果可以,自身进行一次移动,把pos之前的空间利用上,更新自身的pos和limt。最后在把data中数据通过arraycopy把data中的数据移动到sink的limt后面即可,然后更新sink的limit和当前Segment的pos。


  }

 

posted @ 2023-04-12 11:03  lianzhen  阅读(69)  评论(0编辑  收藏  举报