作者簡(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 方法,代碼如下所示: