Okio庫是一個由square公司開發的,它補充了Java.io和java.nio的不足,以便能夠更加友善,快速的通路、存儲和處理你的資料。而OkHttp的底層也使用該庫作為支援。
Okio底層通過兩種包含多種能力的類提供豐富的API。它們是ByteStrings 和 Buffers。
ByteStrings
這個類提供了豐富的編碼和解碼操作,比如hex, base64, and UTF-8。
Buffers
它提供了位元組緩沖區操作,類似隊列結果,從頭部讀取資料,寫入的資料添加到尾部。
由于内部高效的算法,ByteStrings和Buffer很大程度上節約了CPU資源和記憶體。
Okio有自己的流類型,叫做Source和Sink。Source類似java io中的InputStream,Sink類似java io中的OutputStream,但是又有一些關鍵的不同點:
1、Timeouts
讀寫提供了逾時檢查
2、接口實作很簡單
Source隻定義了三個方法:read(),close(),timeout().
3、使用更加簡單
BufferedSource和BufferedSink基本上提供了你需要的任何功能
Okio中有兩個關鍵的接口,Sink和Source,這兩個接口都繼承了Closeable接口;而Sink可以簡單的看做OutputStream,Source可以簡單的看做InputStream。而這兩個接口都是支援讀寫逾時設定的。結構圖如下
它們各自有一個支援緩沖區的子類接口,BufferedSink和BufferedSource,而BufferedSink有一個實作類RealBufferedSink,BufferedSource有一個實作類RealBufferedSource;此外,Sink和Source它門還各自有一個支援gzip壓縮的實作類GzipSink和GzipSource;一個具有委托功能的抽象類ForwardingSink和ForwardingSource;還有一個實作類便是InflaterSource和DeflaterSink,這兩個類主要用于壓縮,為GzipSink和GzipSource服務;整體的結構圖如下
BufferedSink中定義了一系列寫入緩存區的方法,比如write方法寫byte數組,writeUtf8寫字元串,還有一些列的writeByte,writeString,writeShort,writeInt,writeLong,writeDecimalLong等等方法;
BufferedSource定義的方法和BufferedSink極為相似,隻不過一個是寫一個是讀,基本上都是一一對應的,如readUtf8,readByte,readString,readShort,readInt等等等等。這兩個接口中的方法有興趣的點源碼進去看就可以了。
而這兩個支援緩沖區的接口的實作類RealBufferedSink和RealBufferedSource都是通過包裝一個Sink+Buffer或者Source+Buffer來進行實作的。如下圖所示
拿RealBufferedSink來舉例,實際調用的write的一系列方法,都是直接的對成員變量buffer進行的操作,當寫入buffer成功後,最後會調用一個方法将buffer中的内容寫入到sink中去,我們随便拿一個方法看一下
@Override public BufferedSink writeShort(int s) throws IOException {
if (closed) throw new IllegalStateException("closed");
buffer.writeShort(s);
return emitCompleteSegments();
}
可以看到,首先會判斷closed成員變量是否是标記着關閉,如果已經關閉了則扔出一個異常,否則将内容寫入到buffer,寫入完成後調用了一個emitCompleteSegments的方法,該方法中做了什麼呢
@Override public BufferedSink emitCompleteSegments() throws IOException {
if (closed) throw new IllegalStateException("closed");
long byteCount = buffer.completeSegmentByteCount();
if (byteCount > 0) sink.write(buffer, byteCount);
return this;
}
這兩個實作類的内部的所有方法都是類似的,這裡不一一展開。
而這一切的背後都是一個叫做Buffer的類在支援着緩沖區,Buffer是BufferedSink和BufferedSource的實作類,是以它既可以用來讀資料,也可以用來寫資料,其内部使用了一個Segment和SegmentPool,維持着一個連結清單,其循環利用的機制和Android中Message的利用機制是一模一樣的。
/**
* A collection of unused segments, necessary to avoid GC churn and zero-fill.
* This pool is a thread-safe static singleton.
*/
final class SegmentPool {
/** 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.
/** Singly-linked list of segments. */
static Segment next;
/** Total bytes in this pool. */
static long byteCount;
private SegmentPool() {
}
static Segment take() {
synchronized (SegmentPool.class) {
if (next != null) {
Segment result = next;
next = result.next;
result.next = null;
byteCount -= Segment.SIZE;
return result;
}
}
return new Segment(); // Pool is empty. Don't zero-fill while holding a lock.
}
static void recycle(Segment segment) {
if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
if (segment.shared) return; // This segment cannot be recycled.
synchronized (SegmentPool.class) {
if (byteCount + Segment.SIZE > MAX_SIZE) return; // Pool is full.
byteCount += Segment.SIZE;
segment.next = next;
segment.pos = segment.limit = 0;
next = segment;
}
}
}
内部一個成員變量next指向連結清單下一個元素,take方法首先判斷池中是否存在可用的,存在則傳回,不存在則new一個,而recycle則是将不再使用的Segment重新扔到池中去。進而達到一個Segment池的作用。
而Okio暴露給外部使用的類便是Okio這個類,其内部有大量的靜态方法,包括通過一個Source獲得BufferedSource,通過一個Sink獲得一個BufferedSink。這個過程很簡單,我們調用Okio的buffer方法即可傳回我們需要的,如下
Okio.buffer(source);
Okio.buffer(sink);
但是上面兩個方法需要傳遞一個Sink或者Source,那麼這個Sink和Source又是如何獲得的呢。其實方法也在Okio這個類中。我們可以調用sink方法獲得一個Sink,調用source方法獲得一個Source,而資料的來源或者目的可以是一個File,一個輸入或者輸出流,一個Socket連結等等。如下
Okio.sink(File file);
Okio.appendingSink(File file);//内容可追加
Okio.sink(Path path, OpenOption... options);
Okio.sink(OutputStream out);
Okio.sink(Socket socket);
Okio.source(File file);
Okio.source(Path path, OpenOption... options);
Okio.source(InputStream in);
Okio.source(Socket socket);
下面舉兩個案例來簡單說明如何使用Okio。
1、檔案讀寫
//寫入資料到檔案中
Sink sink = Okio.sink(file);
BufferedSink bufferedSink = Okio.buffer(sink);
bufferedSink.writeUtf8("我是一個中國人");
bufferedSink.close();
//從檔案中讀取資料
Source source = Okio.source(file);
BufferedSource bufferedSource = Okio.buffer(source);
String data = bufferedSource.readUtf8();
bufferedSource.close();
2、Gzip壓縮和讀取
//zip壓縮
GzipSink gzipSink = new GzipSink(Okio.sink(file));
BufferedSink bufferedSink = Okio.buffer(gzipSink);
bufferedSink.writeUtf8("this is zip file");
bufferedSink.flush();
bufferedSink.close();
//讀取zip
GzipSource gzipSource = new GzipSource(Okio.source(file));
BufferedSource bufferedSource = Okio.buffer(gzipSource);
String s = bufferedSource.readUtf8();
bufferedSource.close();