12 | Android 高級(jí)進(jìn)階(源碼剖析篇) Square 高效易用的 IO 框架 okio(五)

作者簡(jiǎn)介:ASCE1885, 《Android 高級(jí)進(jìn)階》作者。
本文由于潛在的商業(yè)目的,未經(jīng)授權(quán)不開放全文轉(zhuǎn)載許可,謝謝!
本文分析的源碼版本已經(jīng) fork 到我的 Github。

回眸

在本系列第二篇文章中,我們專門介紹了 okio 中的 Source,但其中 InflaterSource 和 GzipSource 由于涉及 segment 的相關(guān)知識(shí),因此沒(méi)有作介紹,本文就來(lái)補(bǔ)充一下。由于壓縮和解壓算法是配套的,因此,本文同時(shí)也會(huì)介紹 DeflaterSink 和 GzipSink。顧名思義,

  • DeflaterSink 是一個(gè)具備壓縮功能的輸出流,將數(shù)據(jù)寫入這個(gè) sink 時(shí),它會(huì)使用 DEFLATE 算法對(duì)數(shù)據(jù)進(jìn)行壓縮操作
  • InflaterSource 是一個(gè)具備解壓功能的輸入流,從這個(gè) source 中讀取數(shù)據(jù)時(shí),它會(huì)使用 DEFLATE 算法對(duì)自身的數(shù)據(jù)進(jìn)行解壓操作
  • GzipSink 是一個(gè)具備壓縮功能的輸出流,將數(shù)據(jù)寫入這個(gè) sink 時(shí),它會(huì)使用 GZIP 算法對(duì)數(shù)據(jù)進(jìn)行壓縮操作
  • GzipSource 是一個(gè)具備解壓功能的輸入流,從這個(gè) source 中讀取數(shù)據(jù)時(shí),它會(huì)使用 GZIP 算法對(duì)數(shù)據(jù)進(jìn)行解壓操作

下面我們分別進(jìn)行介紹。

PS:okio 中輸入流 Source 和輸出流 Sink 的字節(jié)數(shù)據(jù)都是存放在自身 segment 鏈表的元素中,因此,在下面代碼中我們可以看到大量的對(duì) segment 的操作,大家請(qǐng)自覺(jué)復(fù)習(xí)本系列第三篇文章。

DEFLATE 算法

DEFLATE 是一個(gè)同時(shí)運(yùn)用了 LZ77 算法和哈夫曼編碼的無(wú)損數(shù)據(jù)壓縮算法,在 Java 中,提供了 Deflater 工具類來(lái)實(shí)現(xiàn)數(shù)據(jù)壓縮,該 API 使用的示例如下所示:

String inputString = "blahblahblah";
byte[] input = inputString.getBytes("UTF-8"); // 未壓縮的數(shù)據(jù)

byte[] output = new byte[100]; // 存放壓縮后的數(shù)據(jù)
Deflater compresser = new Deflater();
compresser.setInput(input); // 設(shè)置需要壓縮的數(shù)據(jù)
compresser.finish(); // 表示需要壓縮的數(shù)據(jù)設(shè)置完成
int compressedDataLength = compresser.deflate(output); // 執(zhí)行壓縮操作
compresser.end(); // 關(guān)閉壓縮器,并丟棄任何還未壓縮的數(shù)據(jù)

提供 Inflater 工具類來(lái)實(shí)現(xiàn)數(shù)據(jù)的解壓,該 API 使用的示例如下所示,為了演示方便,承接上面的壓縮代碼,使用其中的某些變量:

Inflater decompresser = new Inflater();
decompresser.setInput(output, 0, compressedDataLength); // 設(shè)置需要解壓的數(shù)據(jù)
byte[] result = new byte[100];
int resultLength = decompresser.inflate(result); // 執(zhí)行實(shí)際的解壓操作
decompresser.end(); // 關(guān)閉解壓器,并丟棄任何還未解壓的數(shù)據(jù)

可見(jiàn),API 的使用還是挺簡(jiǎn)單的,接下來(lái)看看怎么將 DEFLATE 算法和 okio 相結(jié)合實(shí)現(xiàn)數(shù)據(jù)流的壓縮和解壓,首先來(lái)看壓縮操作,也就是 DeflaterSink 這個(gè)類。

DeflaterSink

DeflaterSink 類從某個(gè)輸入流的 segment 鏈表中讀取數(shù)據(jù),然后使用 DEFLATE 對(duì)數(shù)據(jù)進(jìn)行壓縮后存放到自身的 segment 鏈表中,核心算法在 write 方法中,該方法首先進(jìn)行參數(shù)校驗(yàn),然后每次以一個(gè) segment 為單位,循環(huán)從輸入流中讀取數(shù)據(jù)進(jìn)行壓縮,直到達(dá)到 write 方法指定的壓縮字節(jié)數(shù),具體的邏輯我們直接看代碼和注釋:

@Override public void write(Buffer source, long byteCount) throws IOException {
    // 參數(shù)校驗(yàn)
    checkOffsetAndCount(source.size, 0, byteCount);

    // byteCount表示剩余的需要壓縮的字節(jié)數(shù)
    while (byteCount > 0) {
      // 每次從輸入流source中取出segment鏈表的第一個(gè)元素
      Segment head = source.head;

      // 根據(jù)segment中的未讀的數(shù)據(jù)大小和byteCount來(lái)決定要壓縮的數(shù)據(jù)字節(jié)數(shù)
      int toDeflate = (int) Math.min(byteCount, head.limit - head.pos);

      // 設(shè)置需要壓縮的數(shù)據(jù)
      deflater.setInput(head.data, head.pos, toDeflate);

      // 將原始數(shù)據(jù)壓縮后存入當(dāng)前sink中
      deflate(false);

      // 移動(dòng)數(shù)據(jù)指針
      source.size -= toDeflate;
      head.pos += toDeflate;
      if (head.pos == head.limit) {
        // 輸入流已經(jīng)沒(méi)有未壓縮數(shù)據(jù),釋放資源
        source.head = head.pop();
        SegmentPool.recycle(head);
      }

      // 重新計(jì)算未壓縮數(shù)據(jù)字節(jié)數(shù)
      byteCount -= toDeflate;
    }
}

可以看到,上面代碼操作的主要是輸入流的 segment,而實(shí)際的壓縮操作和對(duì)輸出流的 segment 的申請(qǐng)等操作則在 deflate 私有方法中,如下所示:

private void deflate(boolean syncFlush) throws IOException {
    Buffer buffer = sink.buffer();
    while (true) {
      // 在當(dāng)前sink中申請(qǐng)一個(gè)位于segment鏈表尾部的segment,用來(lái)存放壓縮后的數(shù)據(jù)
      Segment s = buffer.writableSegment(1);

      // 調(diào)用Deflater類的壓縮方法執(zhí)行實(shí)際的壓縮操作,壓縮后的數(shù)據(jù)存放在s.data中
      int deflated = syncFlush
          ? deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit, Deflater.SYNC_FLUSH)
          : deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit);

      if (deflated > 0) {
        // 有數(shù)據(jù)被壓縮了
        s.limit += deflated;
        buffer.size += deflated;
        sink.emitCompleteSegments();
      } else if (deflater.needsInput()) {
        // 沒(méi)有數(shù)據(jù)被壓縮,同時(shí)也不存在需要壓縮的數(shù)據(jù),則釋放申請(qǐng)的segment
        if (s.pos == s.limit) {
          buffer.head = s.pop();
          SegmentPool.recycle(s);
        }
        return;
      }
    }
}

總結(jié)起來(lái),DeflaterSink 的核心算法就是將輸入流中需要壓縮的數(shù)據(jù),以 segment 為單位循環(huán)進(jìn)行壓縮后存放到輸出流的 segment 中,僅此而已,當(dāng)然細(xì)節(jié)是魔鬼,一切的細(xì)節(jié)都在上面的代碼中,大家可以細(xì)細(xì)品味。

InflaterSource

和 DeflaterSink 相對(duì)應(yīng),InflaterSource 的核心算法就是將自身需要解壓的數(shù)據(jù),以 segment 為單位進(jìn)行解壓后存放到輸出流中。我們來(lái)看下核心的 read 方法,該方法首先進(jìn)行參數(shù)校驗(yàn),然后從當(dāng)前輸入流 source 中讀取需要解壓的數(shù)據(jù)填充給解壓器 inflater,然后從輸出流 sink 中申請(qǐng) segment,用來(lái)存放解壓后的數(shù)據(jù),代碼和注釋如下所示:

@Override public long read(
      Buffer sink, long byteCount) throws IOException {
    // 參數(shù)校驗(yàn)
    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
    if (closed) throw new IllegalStateException("closed");
    if (byteCount == 0) return 0;

    while (true) {
      // 從當(dāng)前source中獲取需要解壓的數(shù)據(jù)并設(shè)置給解壓器inflater
      boolean sourceExhausted = refill();

      try {
        // 從輸出流中申請(qǐng)segment
        Segment tail = sink.writableSegment(1);

        // 調(diào)用Inflater類的解壓方法執(zhí)行實(shí)際的解壓操作,并將解壓后的數(shù)據(jù)存放到輸出流sink的tail.data中
        int bytesInflated = inflater.inflate(tail.data, tail.limit, Segment.SIZE - tail.limit);
        if (bytesInflated > 0) {
          // 有數(shù)據(jù)被解壓,返回被解壓的字節(jié)數(shù)
          tail.limit += bytesInflated;
          sink.size += bytesInflated;
          return bytesInflated;
        }

        // 沒(méi)有需要解壓的數(shù)據(jù)
        if (inflater.finished() || inflater.needsDictionary()) {
          // 清理未解壓的數(shù)據(jù)
          releaseInflatedBytes();
          if (tail.pos == tail.limit) {
            // 回收前面申請(qǐng)的segment
            sink.head = tail.pop();
            SegmentPool.recycle(tail);
          }
          return -1;
        }
        if (sourceExhausted) throw new EOFException("source exhausted prematurely");
      } catch (DataFormatException e) {
        throw new IOException(e);
      }
    }
}

需要注意的一點(diǎn)是,上面代碼中每次解壓都是把當(dāng)前 segment 中剩余未讀的所有數(shù)據(jù)(大小是 Segment.SIZE - tail.limit)進(jìn)行解壓,而入?yún)?byteCount 沒(méi)有使用到,這是有問(wèn)題的,這部分代碼如下:

Segment tail = sink.writableSegment(1);
int bytesInflated = inflater.inflate(tail.data, tail.limit, Segment.SIZE - tail.limit);

正確的做法是把入?yún)?byteCount 考慮進(jìn)來(lái),取亮著的最小值作為實(shí)際解壓的數(shù)據(jù)大小,如下所示:

Segment tail = sink.writableSegment(1);
int toRead = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
int bytesInflated = inflater.inflate(tail.data, tail.limit, toRead);

通過(guò)查看 Github 上最新的 okio 的代碼,我們可以發(fā)現(xiàn)這個(gè)問(wèn)題已經(jīng)被解決了,具體可以參見(jiàn)這次提交。

好,我們繼續(xù)分析,在上面 read 方法中調(diào)用了另外兩個(gè)私有方法,其中 refill 是重新給解壓器設(shè)置需要解壓的數(shù)據(jù),也就是調(diào)用 Inflater 的 setInput 方法,代碼如下所示:

還有 52% 的精彩內(nèi)容
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
支付 ¥5.20 繼續(xù)閱讀

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容