摘要:封裝了和,并且有多個(gè)優(yōu)點(diǎn)提供超時(shí)機(jī)制不需要人工區(qū)分字節(jié)流與字符流,易于使用易于測(cè)試本文先介紹的基本用法,然后分析源碼中數(shù)據(jù)讀取的流程。和分別用于提供字節(jié)流和接收字節(jié)流,對(duì)應(yīng)于和。和則是保存了相應(yīng)的緩存數(shù)據(jù)用于高效讀寫。
簡(jiǎn)介
Okio 是 square 開(kāi)發(fā)的一個(gè) Java I/O 庫(kù),并且也是 OkHttp 內(nèi)部使用的一個(gè)組件。Okio 封裝了 java.io 和 java.nio,并且有多個(gè)優(yōu)點(diǎn):
提供超時(shí)機(jī)制
不需要人工區(qū)分字節(jié)流與字符流,易于使用
易于測(cè)試
本文先介紹 Okio 的基本用法,然后分析源碼中數(shù)據(jù)讀取的流程。
基本用法Okio 的用法很簡(jiǎn)單,下面是讀取和寫入的示例:
// 讀取 InputStream inputStream = ... BufferedSource bufferedSource = Okio.buffer(Okio.source(inputStream)); String line = bufferedSource.readUtf8(); // 寫入 OutputStream outputStream = ... BufferedSink bufferedSink = Okio.buffer(Okio.sink(outputStream)); bufferedSink.writeString("test", Charset.defaultCharset()); bufferedSink.close();
Okio 用 Okio.source 封裝 InputStream,用 Okio.sink 封裝 OutputStream。然后統(tǒng)一交給 Okio.buffer 分別獲得 BufferedSource 和 BufferedSink,這兩個(gè)類提供了大量的讀寫數(shù)據(jù)的方法。BufferedSource 中包含的部分接口如下:
int readInt() throws IOException; long readLong() throws IOException; byte readByte() throws IOException; ByteString readByteString() throws IOException; String readUtf8() throws IOException; String readString(Charset charset) throws IOException;
其中既包含了讀取字節(jié)流,也包含讀取字符流的方法,BufferedSink 則提供了對(duì)應(yīng)的寫入數(shù)據(jù)的方法。
基本框架Okio 中有4個(gè)接口,分別是 Source、Sink、 BufferedSource 和 BufferedSink。Source 和 Sink 分別用于提供字節(jié)流和接收字節(jié)流,對(duì)應(yīng)于 Inpustream 和 OutputStream。BufferedSource 和 BufferedSink 則是保存了相應(yīng)的緩存數(shù)據(jù)用于高效讀寫。這幾個(gè)接口的繼承關(guān)系如下:
從上圖可以看出,Source 和 Sink 提供基本的 read 和 write 方法,而 BufferedSource 和 BufferedSink 則提供了更多的操作數(shù)據(jù)的方法,但這些都是接口,真正實(shí)現(xiàn)的類是 RealBufferedSource 和 RealBufferedSink。
另外還有個(gè)類是 Buffer, 它同時(shí)實(shí)現(xiàn)了 BufferedSource 和 BufferedSink,并且 RealBufferedSource 和 RealbufferedSink 都包含一個(gè) Buffer 對(duì)象,真正的數(shù)據(jù)讀取操作都是交給 Buffer 完成的。
由于 read 和 write 操作類似,下面以 read 的流程對(duì)代碼進(jìn)行分析。
Okio.sourceOkio.source 有幾個(gè)重載的方法,用于封裝輸入流,最終調(diào)用的代碼如下:
private static Source source(final InputStream in, final Timeout timeout) { if (in == null) throw new IllegalArgumentException("in == null"); if (timeout == null) throw new IllegalArgumentException("timeout == null"); return new Source() { @Override public long read(Buffer sink, long byteCount) throws IOException { if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); if (byteCount == 0) return 0; try { timeout.throwIfReached(); Segment tail = sink.writableSegment(1); int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit); int bytesRead = in.read(tail.data, tail.limit, maxToCopy); if (bytesRead == -1) return -1; tail.limit += bytesRead; sink.size += bytesRead; return bytesRead; } catch (AssertionError e) { if (isAndroidGetsocknameError(e)) throw new IOException(e); throw e; } } @Override public void close() throws IOException { in.close(); } @Override public Timeout timeout() { return timeout; } @Override public String toString() { return "source(" + in + ")"; } }; }
從上面代碼可以看出,Okio.source 接受兩個(gè)參數(shù),一個(gè)是 InputStream,另一個(gè)是 Timeout,返回了一個(gè)匿名的 Source 的實(shí)現(xiàn)類。這里主要看一下 read 方法,首先是參數(shù)為空的判斷,然后是從 in 中讀取數(shù)據(jù)到類型為 Buffer 的 sink 中,這段代碼中涉及到 Buffer 以及 Segment,下面先看看這兩個(gè)東西。
Segment在 Okio 中,每個(gè) Segment 代表一段數(shù)據(jù),多個(gè) Segment 串成一個(gè)循環(huán)雙向鏈表。下面是 Segment 的成員變量和構(gòu)造方法:
final class Segment { // segment數(shù)據(jù)的字節(jié)數(shù) static final int SIZE = 8192; // 共享的Segment的最低的數(shù)據(jù)大小 static final int SHARE_MINIMUM = 1024; // 實(shí)際保存的數(shù)據(jù) final byte[] data; // 下一個(gè)可讀的位置 int pos; // 下一個(gè)可寫的位置 int limit; // 保存的數(shù)據(jù)是否是共享的 boolean shared; // 保存的數(shù)據(jù)是否是獨(dú)占的 boolean owner; // 鏈表中下一個(gè)節(jié)點(diǎn) Segment next; // 鏈表中上一個(gè)節(jié)點(diǎn) Segment prev; Segment() { this.data = new byte[SIZE]; this.owner = true; this.shared = false; } Segment(Segment shareFrom) { this(shareFrom.data, shareFrom.pos, shareFrom.limit); shareFrom.shared = true; } Segment(byte[] data, int pos, int limit) { this.data = data; this.pos = pos; this.limit = limit; this.owner = false; this.shared = true; } ... }
變量的含義已經(jīng)寫在了注釋中,可以看出 Segment 中的數(shù)據(jù)保存在一個(gè)字節(jié)數(shù)組中,并提供了一些變量標(biāo)識(shí)讀與寫的位置。Segment 既然是鏈表中的節(jié)點(diǎn),下面看一下插入與刪除的方法:
// 在當(dāng)前Segment后面插入一個(gè)Segment public Segment push(Segment segment) { segment.prev = this; segment.next = next; next.prev = segment; next = segment; return segment; } // 從鏈表中刪除當(dāng)前Segment,并返回其后繼節(jié)點(diǎn) public @Nullable Segment pop() { Segment result = next != this ? next : null; prev.next = next; next.prev = prev; next = null; prev = null; return result; }
插入與刪除的代碼其實(shí)就是數(shù)據(jù)結(jié)構(gòu)中鏈表的操作。
Buffer下面看看 Buffer 是如何使用 Segment 的。Buffer 中有兩個(gè)重要變量:
@Nullable Segment head; long size;
一個(gè)是 head,表示這個(gè) Buffer 保存的 Segment 鏈表的頭結(jié)點(diǎn)。還有一個(gè) size,用于記錄 Buffer 當(dāng)前的字節(jié)數(shù)。
在上面 Okio.source 中生成的匿名的 Source 的 read 方法中,要讀取數(shù)據(jù)到 Buffer 中,首次是調(diào)用了 writableSegment,這個(gè)方法是獲取一個(gè)可寫的 Segment,代碼如下所示:
Segment writableSegment(int minimumCapacity) { if (minimumCapacity < 1 || minimumCapacity > Segment.SIZE) throw new IllegalArgumentException(); if (head == null) { head = SegmentPool.take(); // Acquire a first segment. return head.next = head.prev = head; } Segment tail = head.prev; if (tail.limit + minimumCapacity > Segment.SIZE || !tail.owner) { tail = tail.push(SegmentPool.take()); // Append a new empty segment to fill up. } return tail; }
獲取 Segment 的邏輯是先判斷 Buffer 是否有了 Segment 節(jié)點(diǎn),沒(méi)有就先去 SegmentPool 中取一個(gè),并且將首尾相連,形成循環(huán)鏈表。如果已經(jīng)有了,找到末尾的 Segment,判斷其剩余空間是否滿足,不滿足就再?gòu)?SegmentPool 中獲取一個(gè)新的 Segment 添加到末尾。最后,返回末尾的 Segment 用于寫入。
SegmentPool 用于保存廢棄的 Segment,其中有兩個(gè)方法,take 從中獲取,recycle 用于回收。
上面 Okio.buffer(Okio.source(in)) 最終得到的是 RealBufferedSource,這個(gè)類中持有一個(gè) Buffer 對(duì)象和一個(gè) Source 對(duì)象,真正的讀取操作由這兩個(gè)對(duì)象合作完成。下面是 readString 的代碼:
@Override public String readString(long byteCount, Charset charset) throws IOException { require(byteCount); if (charset == null) throw new IllegalArgumentException("charset == null"); return buffer.readString(byteCount, charset); } @Override public void require(long byteCount) throws IOException { if (!request(byteCount)) throw new EOFException(); } // 從source中讀取數(shù)據(jù)到buffer中 @Override public boolean request(long byteCount) throws IOException { if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); if (closed) throw new IllegalStateException("closed"); while (buffer.size < byteCount) { if (source.read(buffer, Segment.SIZE) == -1) return false; } return true; }
首先是從 Source 中讀取數(shù)據(jù)到 Buffer 中,然后調(diào)用 buffer.readstring 方法得到最終的字符串。下面是 readString 的代碼:
@Override public String readString(long byteCount, Charset charset) throws EOFException { checkOffsetAndCount(size, 0, byteCount); if (charset == null) throw new IllegalArgumentException("charset == null"); if (byteCount > Integer.MAX_VALUE) { throw new IllegalArgumentException("byteCount > Integer.MAX_VALUE: " + byteCount); } if (byteCount == 0) return ""; Segment s = head; if (s.pos + byteCount > s.limit) { // 如果string跨多個(gè)Segment,委托給readByteArray去讀 return new String(readByteArray(byteCount), charset); } // 將字節(jié)序列轉(zhuǎn)換成String String result = new String(s.data, s.pos, (int) byteCount, charset); s.pos += byteCount; size -= byteCount; // 如果pos==limit,回收這個(gè)Segment if (s.pos == s.limit) { head = s.pop(); SegmentPool.recycle(s); } return result; }
在上面的代碼中,便是從 Buffer 的 Segment 鏈表中讀取數(shù)據(jù)。如果 String 跨多個(gè) Segment,那么調(diào)用 readByteArray 循環(huán)讀取字節(jié)序列。最終將字節(jié)序列轉(zhuǎn)換為 String 對(duì)象。如果 Segment 的 pos 等于 limit,說(shuō)明這個(gè) Segment 的數(shù)據(jù)已經(jīng)全部讀取完畢,可以回收,放入 SegmentPool。
Okio 讀取數(shù)據(jù)的時(shí)候統(tǒng)一將輸入流看成是字節(jié)序列,讀入 Buffer 后在用到的時(shí)候再轉(zhuǎn)換,例如上面讀取 String 時(shí)將字節(jié)序列進(jìn)行了轉(zhuǎn)換。其它還有很多類型,如下面是 readInt 的代碼:
@Override public int readInt() { if (size < 4) throw new IllegalStateException("size < 4: " + size); Segment segment = head; int pos = segment.pos; int limit = segment.limit; // If the int is split across multiple segments, delegate to readByte(). if (limit - pos < 4) { return (readByte() & 0xff) << 24 | (readByte() & 0xff) << 16 | (readByte() & 0xff) << 8 | (readByte() & 0xff); } byte[] data = segment.data; int i = (data[pos++] & 0xff) << 24 | (data[pos++] & 0xff) << 16 | (data[pos++] & 0xff) << 8 | (data[pos++] & 0xff); size -= 4; if (pos == limit) { head = segment.pop(); SegmentPool.recycle(segment); } else { segment.pos = pos; } return i; }
Buffer 使用 Segment 鏈表保存數(shù)據(jù),有個(gè)好處是在不同的 Buffer 之間移動(dòng)數(shù)據(jù)只需要轉(zhuǎn)移其字節(jié)序列的擁有權(quán),如 copyTo(Buffer out, long offset, long byteCount) 代碼所示:
public Buffer copyTo(Buffer out, long offset, long byteCount) { if (out == null) throw new IllegalArgumentException("out == null"); checkOffsetAndCount(size, offset, byteCount); if (byteCount == 0) return this; out.size += byteCount; // Skip segments that we aren"t copying from. Segment s = head; for (; offset >= (s.limit - s.pos); s = s.next) { offset -= (s.limit - s.pos); } // Copy one segment at a time. for (; byteCount > 0; s = s.next) { Segment copy = new Segment(s); copy.pos += offset; copy.limit = Math.min(copy.pos + (int) byteCount, copy.limit); if (out.head == null) { out.head = copy.next = copy.prev = copy; } else { out.head.prev.push(copy); } byteCount -= copy.limit - copy.pos; offset = 0; } return this; }
其中并沒(méi)有拷貝字節(jié)數(shù)據(jù),只是鏈表的相關(guān)操作。
總結(jié)Okio 讀取數(shù)據(jù)的流程基本就如本文所分析的,寫入操作與讀取是類似的。Okio 通過(guò) Source 與 Sink 標(biāo)識(shí)輸入流與輸出流。在 Buffer 中使用 Segment 鏈表的方式保存字節(jié)數(shù)據(jù),并且通過(guò) Segment 擁有權(quán)的共享避免了數(shù)據(jù)的拷貝,通過(guò) SegmentPool 避免了廢棄數(shù)據(jù)的GC,使得 Okio 成為一個(gè)高效的 I/O 庫(kù)。Okio 還有一個(gè)優(yōu)點(diǎn)是超時(shí)機(jī)制,具體內(nèi)容可進(jìn)入下一篇:Okio 源碼解析(二):超時(shí)機(jī)制
如果我的文章對(duì)您有幫助,不妨點(diǎn)個(gè)贊支持一下(^_^)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/68152.html
摘要:內(nèi)部會(huì)新開(kāi)一個(gè)叫做的線程,根據(jù)超時(shí)時(shí)間依次處理鏈表的節(jié)點(diǎn)??偨Y(jié)通過(guò)以及分別提供了同步超時(shí)和異步超時(shí)功能,同步超時(shí)是在每次讀取數(shù)據(jù)前判斷是否超時(shí),異步超時(shí)則是將組成有序鏈表,并且開(kāi)啟一個(gè)線程來(lái)監(jiān)控,到達(dá)超時(shí)則觸發(fā)相關(guān)操作。 簡(jiǎn)介 上一篇文章(Okio 源碼解析(一):數(shù)據(jù)讀取流程)分析了 Okio 數(shù)據(jù)讀取的流程,從中可以看出 Okio 的便捷與高效。Okio 的另外一個(gè)優(yōu)點(diǎn)是提供了超時(shí)...
摘要:通過(guò)多個(gè)裝飾類實(shí)現(xiàn)責(zé)任鏈模式,它將對(duì)一個(gè)輸入流的不同處理分散到不同的中去。 1、基本概念 1.1、InputStream 最基本的字節(jié)輸入流,抽象類,定義了讀取原始字節(jié)的所有基本方法1.1.1、public abstract int read() throws IOException 讀取一個(gè)字節(jié)的方法,最基礎(chǔ)的方法1.1.2、public int read(byte b[], in...
摘要:使用前準(zhǔn)備配置添加網(wǎng)絡(luò)權(quán)限異步請(qǐng)求慣例,請(qǐng)求百度可以省略,默認(rèn)是請(qǐng)求請(qǐng)求成功與版本并沒(méi)有什么不同,比較郁悶的是回調(diào)仍然不在線程。 前言 上一篇介紹了OkHttp2.x的用法,這一篇文章我們來(lái)對(duì)照OkHttp2.x版本來(lái)看看,OkHttp3使用起來(lái)有那些變化。當(dāng)然,看這篇文章前建議看一下前一篇文章Android網(wǎng)絡(luò)編程(五)OkHttp2.x用法全解析。 1.使用前準(zhǔn)備 Android ...
閱讀 2291·2021-11-22 15:29
閱讀 4141·2021-11-04 16:13
閱讀 1019·2019-08-29 16:58
閱讀 360·2019-08-29 16:08
閱讀 1502·2019-08-23 17:56
閱讀 2423·2019-08-23 17:06
閱讀 3189·2019-08-23 16:55
閱讀 2086·2019-08-23 16:22