Okio組件源碼分析

Okio庫 組件分析

Segment

  Segment 是數據操作過程中,保存數據的地方,內部持有一個 限制最大爲 8192 byte 的byte[]數組用來存儲數據。Segment 的結構是 雙向鏈表的結構,所以提供了 pop push 對結構的操作。Segment 採用鏈表的結構,內部使用數組來保存存儲數據是一個數據操作的這種方案,鏈表使得插入刪除更快,數組可以保證讀取更快。Segment 中的優化還有 Segment 數據快的共享 以及 數據的壓縮機制。

  /**
   * Call this when the tail and its predecessor may both be less than half
   * full. This will copy data so that segments can be recycled.
   */
  public void compact() { //壓縮當前Segment
    if (prev == this) throw new IllegalStateException();
    if (!prev.owner) return; // Cannot compact: prev isn't writable.
    int byteCount = limit - pos;
    int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
    if (byteCount > availableByteCount) return; // Cannot compact: not enough writable space.
    writeTo(prev, byteCount); //判斷可以則寫入
    pop(); // pop當前segment
    SegmentPool.recycle(this); // pool 回收
  }

SegmentPool

  Segment的創建以及回收。因爲Segment本身是鏈式結構,所以SegmentPool,內部使用 一個 next 作爲 當前回收池第一個Segment的引用。SegmentPool 內部對 池的大小做了限制, Pool的最大字節是 64kb。不過看 這個 todo的註釋,貌似也不確定 MAX_SIZE 設置爲多少合適。

/** The maximum number of bytes to pool. */
  // TODO: Is 64 KiB a good maximum size? Do we ever have that many idle segments?
  static final long MAX_SIZE = 64 * 1024; // 64 KiB.

Source 與 Sink

  類似 java.io 中流的概念,流的設計可以層層嵌套,功能加強,Source 表示輸入流,Sink 表示輸出流。BufferedSourceBufferdSink 提供了 數據的緩衝處理。Buffer的封裝後,內部使用 Segment 鏈表來保存數據,在具體的實現上,okio 還提供了 數據的共享。SourceSink 抽象了數據的來源,可以是 磁盤內部網絡等。

對Buffer 設計的理解

  這裏,實際上對Buffer 這一層的設計,我只理解了是要做數據的暫存,避免持續的讀寫直接從管道對應的底層硬件操作。Okio中,Buffer類 實現了 BufferedSource, BufferedSink,也就是Buffer類將Source 和 Sink的操作都集合在一起,而在實際的處理過程中,是由 RealBufferedSinkRealBufferedSource 處理 寫入 和輸出 的一些不同之處,共同的業務邏輯調用Buffer類來實現,(Real類 是 Buffere 的代理類)分別這裏貼上一段別人對okio中Buffer這一層設計的理解。

public final class Buffer implements BufferedSource, BufferedSink, Cloneable {
  private static final byte[] DIGITS =
      { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
  static final int REPLACEMENT_CHARACTER = '\ufffd';

  Segment head;
  long size;

  public Buffer() {
  }
}  
final class RealBufferedSource implements BufferedSource {
  public final Buffer buffer = new Buffer();
  public final Source source;
  boolean closed;

  RealBufferedSource(Source source) {
    if (source == null) throw new NullPointerException("source == null");
    this.source = source;
  }
  //... 一些數據操作
}

Okio對數據的操作需要先從 管道(類似InputStream) 讀到 Buffer 裏(require),這個過程實際由匿名 Source 完成(提供了超時控制),再把數據從 Buffer 中讀出來返回。

其實我們如果粗略看一下 RealBufferedSource 和 RealBufferedSink 這兩個類,我們就會發現,它們讀寫邏輯的實現都比較繞:讀操作都是先把數據從 Source 讀到 Buffer,再把數據從 Buffer 讀到輸出(返回值或傳入的輸出參數);寫操作都是先把數據從輸入寫到 Buffer,再把數據從 Buffer 寫到 Sink。

爲什麼要這麼倒騰? 讓我們從功能需求和設計方案來考慮。

BufferedSource 要提供各種形式的讀取操作,還有查找與判等操作。大家可能會想,那我就在實現類中自己實現不就好了嗎?幹嘛要經過 Buffer 中轉呢?這裏我們實現的時候,需要考慮效率的問題,而且不僅 BufferedSource 需要高效實現,BufferedSink 也需要高效實現,這兩者的高效實現技巧,很大部分都是共通的,所以爲了避免同樣的邏輯重複兩遍,Okio 就直接把讀寫操作都實現在了 Buffer 這一個類中,這樣邏輯更加緊湊,更加內聚。而且還能直接滿足我們對於“兩用數據緩衝區”的需求:既可以從頭部讀取數據,也能向尾部寫入數據。至於我們單獨的讀寫操作需求,Okio 就爲 Buffer 分別提供了委託類:RealBufferedSource 和 RealBufferedSink,實現好 Buffer 之後,它們兩者的實現將非常簡潔(前者 450 行,後者 250 行)。  

ByteString

  在大多數語言,包括Java中,String 是一個不可變的對象,ByteString 也是一個 不可變對象,ByteString 對象代表一個 immutable 的字節序列,主要的字段是 data 以及 utf8data即ByteString 構造時傳入的 byte數組utf8 字段是該字節的 utf-8表示 ,這是一個 lazily computed 字段,即只有真正需要獲取(也僅在第一次,因爲byteString的設計是不可變的)纔會去做轉換。 ByteString 是思想是 以空間換時間來提高時間上的效率。

  ByteString 的構造函數並沒有 clone data,因爲這個類內部本身並不會對 data做任何的修改,既然是immutable,也就沒必要浪費這個空間了。

  ByteString(byte[] data) {
    this.data = data; // Trusted internal constructor doesn't clone data.
  }

ByteString 提供了很多靜態幫助方法 類型包括 數據的加密(包括 md5、sha1、等)、數據的截取、大小寫轉換、數據IO操作等,這些方法的返回都是一個新的 ByteString,這也是 immutable 的弊端,在複雜操作過程中會創建大量的對象。

TimeOut源碼分析

見我的TimeOut源碼分析

用例

  官網上讀取 png 圖片的例子

private static final ByteString PNG_HEADER = ByteString.decodeHex("89504e470d0a1a0a");

public void decodePng(InputStream in) throws IOException {
  BufferedSource pngSource = Okio.buffer(Okio.source(in));//inputstream 轉化成okio的 Source

  ByteString header = pngSource.readByteString(PNG_HEADER.size());
  if (!header.equals(PNG_HEADER)) {
    throw new IOException("Not a PNG.");
  }

  while (true) {
    Buffer chunk = new Buffer();

    // Each chunk is a length, type, data, and CRC offset.
    int length = pngSource.readInt();
    String type = pngSource.readUtf8(4);
    pngSource.readFully(chunk, length);
    int crc = pngSource.readInt();

    decodeChunk(type, chunk);
    if (type.equals("IEND")) break;
  }

  pngSource.close();
}

private void decodeChunk(String type, Buffer chunk) {
  if (type.equals("IHDR")) {
    int width = chunk.readInt();
    int height = chunk.readInt();
    System.out.printf("%08x: %s %d x %d%n", chunk.size(), type, width, height);
  } else {
    System.out.printf("%08x: %s%n", chunk.size(), type);
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章