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 表示輸出流。BufferedSource 與 BufferdSink 提供了 數據的緩衝處理。Buffer的封裝後,內部使用 Segment 鏈表來保存數據,在具體的實現上,okio 還提供了 數據的共享。Source 和 Sink 抽象了數據的來源,可以是 磁盤,內部,網絡等。
對Buffer 設計的理解
這裏,實際上對Buffer 這一層的設計,我只理解了是要做數據的暫存,避免持續的讀寫直接從管道對應的底層硬件操作。Okio中,Buffer類 實現了 BufferedSource, BufferedSink,也就是Buffer類將Source 和 Sink的操作都集合在一起,而在實際的處理過程中,是由 RealBufferedSink 和 RealBufferedSource 處理 寫入 和輸出 的一些不同之處,共同的業務邏輯調用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 以及 utf8,data即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);
}
}