前言
大家好凰盔,我是小彭。
今天倦春,我們來(lái)討論一個(gè) Square 開(kāi)源的 I/O 框架 Okio户敬,我們最開(kāi)始接觸到 Okio 框架還是源于 Square 家的 OkHttp 網(wǎng)絡(luò)框架。那么睁本,OkHttp 為什么要使用 Okio尿庐,它相比于 Java 原生 IO 有什么區(qū)別和優(yōu)勢(shì)?今天我們就圍繞這些問(wèn)題展開(kāi)呢堰。
本文源碼基于 Okio v3.2.0抄瑟。
思維導(dǎo)圖
1. 說(shuō)一下 Okio 的優(yōu)勢(shì)?
相比于 Java 原生 IO 框架枉疼,我認(rèn)為 Okio 的優(yōu)勢(shì)主要體現(xiàn)在 3 個(gè)方面:
1皮假、精簡(jiǎn)且全面的 API: 原生 IO 使用裝飾模式,例如使用 BufferedInputStream 裝飾 FileInputStream 文件輸入流骂维,可以增強(qiáng)流的緩沖功能惹资。但是原生 IO 的裝飾器過(guò)于龐大,需要區(qū)分字節(jié)航闺、字符流褪测、字節(jié)數(shù)組猴誊、字符數(shù)組、緩沖等多種裝飾器侮措,而這些恰恰又是最常用的基礎(chǔ)裝飾器懈叹。相較之下,Okio 直接在 BufferedSource 和 BufferedSink 中聚合了原生 IO 中所有基礎(chǔ)的裝飾器分扎,使得框架更加精簡(jiǎn)项阴;
2、基于共享的緩沖區(qū)設(shè)計(jì): 由于 IO 系統(tǒng)調(diào)用存在上下文切換的性能損耗笆包,為了減少系統(tǒng)調(diào)用次數(shù)环揽,應(yīng)用層往往會(huì)采用緩沖區(qū)策略。但是緩沖區(qū)又會(huì)存在副作用庵佣,當(dāng)數(shù)據(jù)從一個(gè)緩沖區(qū)轉(zhuǎn)移到另一個(gè)緩沖區(qū)時(shí)需要拷貝數(shù)據(jù)歉胶,這種內(nèi)存中的拷貝顯得沒(méi)有必要。而 Okio 采用了基于共享的緩沖區(qū)設(shè)計(jì)巴粪,在緩沖區(qū)間轉(zhuǎn)移數(shù)據(jù)只是共享 Segment 的引用通今,而減少了內(nèi)存拷貝。同時(shí) Segment 也采用了對(duì)象池設(shè)計(jì)肛根,減少了內(nèi)存分配和回收的開(kāi)銷(xiāo)辫塌;
3、超時(shí)機(jī)制: Okio 彌補(bǔ)了部分 IO 操作不支持超時(shí)檢測(cè)的缺陷派哲,而且 Okio 不僅支持單次 IO 操作的超時(shí)檢測(cè)臼氨,還支持包含多次 IO 操作的復(fù)合任務(wù)超時(shí)檢測(cè)。
下面芭届,我們將從這三個(gè)優(yōu)勢(shì)展開(kāi)分析:
2. 精簡(jiǎn)的 Okio 框架
先用一個(gè)表格總結(jié) Okio 框架中主要的類(lèi)型:
類(lèi)型 | 描述 |
---|---|
Source | 輸入流 |
Sink | 輸出流 |
BufferedSource | 緩存輸入流接口储矩,實(shí)現(xiàn)類(lèi)是 RealBufferedSource |
BufferedSink | 緩沖輸出流接口,實(shí)現(xiàn)類(lèi)是 RealBufferedSink |
Buffer | 緩沖區(qū)褂乍,由 Segment 鏈表組成 |
Segment | 數(shù)據(jù)片段持隧,多個(gè)片段組成邏輯上連續(xù)數(shù)據(jù) |
ByteString | String 類(lèi) |
Timeout | 超時(shí)控制 |
2.1 Source 輸入流 與 Sink 輸出流
在 Java 原生 IO 中有四個(gè)基礎(chǔ)接口,分別是:
-
字節(jié)流:
InputStream
輸入流和OutputStream
輸出流逃片; -
字符流:
Reader
輸入流和Writer
輸出流屡拨。
而在 Okio 更加精簡(jiǎn),只有兩個(gè)基礎(chǔ)接口褥实,分別是:
-
流:
Source
輸入流和Sink
輸出流呀狼。
Source.kt
interface Source : Closeable {
// 從輸入流讀取數(shù)據(jù)到 Buffer 中(Buffer 等價(jià)于 byte[] 字節(jié)數(shù)組)
// 返回值:-1:輸入內(nèi)容結(jié)束
@Throws(IOException::class)
fun read(sink: Buffer, byteCount: Long): Long
// 超時(shí)控制(詳細(xì)分析見(jiàn)后續(xù)文章)
fun timeout(): Timeout
// 關(guān)閉流
@Throws(IOException::class)
override fun close()
}
Sink.java
actual interface Sink : Closeable, Flushable {
// 將 Buffer 的數(shù)據(jù)寫(xiě)入到輸出流中(Buffer 等價(jià)于 byte[] 字節(jié)數(shù)組)
@Throws(IOException::class)
actual fun write(source: Buffer, byteCount: Long)
// 清空輸出緩沖區(qū)
@Throws(IOException::class)
actual override fun flush()
// 超時(shí)控制(詳細(xì)分析見(jiàn)后續(xù)文章)
actual fun timeout(): Timeout
// 關(guān)閉流
@Throws(IOException::class)
actual override fun close()
}
2.2 InputStream / OutputStream 與 Source / Sink 互轉(zhuǎn)
在功能上,InputStream - Source 和 OutputStream - Sink 分別是等價(jià)的性锭,而且是相互兼容的赠潦。結(jié)合 Kotlin 擴(kuò)展函數(shù)叫胖,兩種接口之間的轉(zhuǎn)換會(huì)非常方便:
- source(): InputStream 轉(zhuǎn) Source草冈,實(shí)現(xiàn)類(lèi)是 InputStreamSource;
- sink(): OutputStream 轉(zhuǎn) Sink,實(shí)現(xiàn)類(lèi)是 OutputStreamSink怎棱;
比較不理解的是: Okio 沒(méi)有提供 InputStreamSource 和 OutputStreamSink 轉(zhuǎn)回 InputStream 和 OutputStream 的方法哩俭,而是需要先轉(zhuǎn)換為 BufferSource 與 BufferSink,再轉(zhuǎn)回 InputStream 和 OutputStream拳恋。
- buffer(): Source 轉(zhuǎn) BufferedSource凡资,Sink 轉(zhuǎn) BufferedSink,實(shí)現(xiàn)類(lèi)分別是 RealBufferedSource 和 RealBufferedSink谬运。
示例代碼
// 原生 IO -> Okio
val source = FileInputStream(File("")).source()
val bufferSource = FileInputStream(File("")).source().buffer()
val sink = FileOutputStream(File("")).sink()
val bufferSink = FileOutputStream(File("")).sink().buffer()
// Okio -> 原生 IO
val inputStream = bufferSource.inputStream()
val outputStream = bufferSink.outputStream()
JvmOkio.kt
// InputStream -> Source
fun InputStream.source(): Source = InputStreamSource(this, Timeout())
// OutputStream -> Sink
fun OutputStream.sink(): Sink = OutputStreamSink(this, Timeout())
private class InputStreamSource(
private val input: InputStream,
private val timeout: Timeout
) : Source {
override fun read(sink: Buffer, byteCount: Long): Long {
if (byteCount == 0L) return 0
require(byteCount >= 0) { "byteCount < 0: $byteCount" }
try {
// 同步超時(shí)監(jiān)控(詳細(xì)分析見(jiàn)后續(xù)文章)
timeout.throwIfReached()
// 讀入 Buffer
val tail = sink.writableSegment(1)
val maxToCopy = minOf(byteCount, Segment.SIZE - tail.limit).toInt()
val bytesRead = input.read(tail.data, tail.limit, maxToCopy)
if (bytesRead == -1) {
if (tail.pos == tail.limit) {
// We allocated a tail segment, but didn't end up needing it. Recycle!
sink.head = tail.pop()
SegmentPool.recycle(tail)
}
return -1
}
tail.limit += bytesRead
sink.size += bytesRead
return bytesRead.toLong()
} catch (e: AssertionError) {
if (e.isAndroidGetsocknameError) throw IOException(e)
throw e
}
}
override fun close() = input.close()
override fun timeout() = timeout
override fun toString() = "source($input)"
}
private class OutputStreamSink(
private val out: OutputStream,
private val timeout: Timeout
) : Sink {
override fun write(source: Buffer, byteCount: Long) {
checkOffsetAndCount(source.size, 0, byteCount)
var remaining = byteCount
// 寫(xiě)出 Buffer
while (remaining > 0) {
// 同步超時(shí)監(jiān)控(詳細(xì)分析見(jiàn)后續(xù)文章)
timeout.throwIfReached()
// 取有效數(shù)據(jù)量和剩余輸出量的較小值
val head = source.head!!
val toCopy = minOf(remaining, head.limit - head.pos).toInt()
out.write(head.data, head.pos, toCopy)
head.pos += toCopy
remaining -= toCopy
source.size -= toCopy
// 指向下一個(gè) Segment
if (head.pos == head.limit) {
source.head = head.pop()
SegmentPool.recycle(head)
}
}
}
override fun flush() = out.flush()
override fun close() = out.close()
override fun timeout() = timeout
override fun toString() = "sink($out)"
}
Okio.kt
// Source -> BufferedSource
fun Source.buffer(): BufferedSource = RealBufferedSource(this)
// Sink -> BufferedSink
fun Sink.buffer(): BufferedSink = RealBufferedSink(this)
2.3 BufferSource 與 BufferSink
在 Java 原生 IO 中隙赁,為了減少系統(tǒng)調(diào)用次數(shù),我們一般不會(huì)直接調(diào)用 InputStream 和 OutputStream梆暖,而是會(huì)使用 BufferedInputStream
和 BufferedOutputStream
包裝類(lèi)增加緩沖功能伞访。
例如,我們希望采用帶緩沖的方式讀取字符格式的文件轰驳,則需要先將文件輸入流包裝為字符流厚掷,再包裝為緩沖流:
Java 原生 IO 示例
// 第一層包裝
FileInputStream fis = new FileInputStream(file);
// 第二層包裝
InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "UTF-8");
// 第三層包裝
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
...
}
// 省略 close
同理,我們?cè)?Okio 中一般也不會(huì)直接調(diào)用 Source 和 Sink级解,而是會(huì)使用 BufferedSource
和 BufferedSink
包裝類(lèi)增加緩沖功能:
Okio 示例
val bufferedSource = file.source()/*第一層包裝*/.buffer()/*第二層包裝*/
while (!bufferedSource.exhausted()) {
val line = bufferedSource.readUtf8Line();
...
}
// 省略 close
網(wǎng)上有資料說(shuō) Okio 沒(méi)有使用裝飾器模式冒黑,所以類(lèi)結(jié)構(gòu)更簡(jiǎn)單。 這么說(shuō)其實(shí)不太準(zhǔn)確勤哗,裝飾器模式本身并不是缺點(diǎn)抡爹,而且從 BufferedSource 和 BufferSink 可以看出 Okio 也使用了裝飾器模式。 嚴(yán)格來(lái)說(shuō)是原生 IO 的裝飾器過(guò)于龐大芒划,而 Okio 的裝飾器更加精簡(jiǎn)豁延。
比如原生 IO 常用的流就有這么多:
原始流: FileInputStream / FileOutputStream 與 SocketInputStream / SocketOutputStream;
基礎(chǔ)接口(區(qū)分字節(jié)流和字符流): InputStream / OutputStream 與 Reader / Writer腊状;
緩存流: BufferedInputStream / BufferedOutputStream 與 BufferedReader / BufferedWriter诱咏;
基本類(lèi)型: DataInputStream / DataOutputStream;
字節(jié)數(shù)組和字符數(shù)組: ByteArrayInputStream / ByteArrayOutputStream 與 CharArrayReader / CharArrayWriter缴挖;
此處省略一萬(wàn)個(gè)字袋狞。
原生 IO 框架
而這么多種流在 Okio 里還剩下多少呢?
- 原始流: FileInputStream / FileOutputStream 與 SocketInputStream / SocketOutputStream映屋;
- 基礎(chǔ)接口: Source / Sink苟鸯;
- 緩存流: BufferedSource / BufferedSink。
Okio 框架
就問(wèn)你服不服棚点?
而且你看哈早处,這些都是平時(shí)業(yè)務(wù)開(kāi)發(fā)中最常見(jiàn)的基本類(lèi)型,原生 IO 把它們都拆分開(kāi)了瘫析,讓問(wèn)題復(fù)雜化了砌梆。反觀 Okio 直接在 BufferedSource 和 BufferedSink 中聚合了原生 IO 中基本的功能默责,而不再需要區(qū)分字節(jié)、字符咸包、字節(jié)數(shù)組桃序、字符數(shù)組、基礎(chǔ)類(lèi)型等等裝飾器烂瘫,確實(shí)讓框架更加精簡(jiǎn)媒熊。
BufferedSource.kt
actual interface BufferedSource : Source, ReadableByteChannel {
actual val buffer: Buffer
// 讀取 Int
@Throws(IOException::class)
actual fun readInt(): Int
// 讀取 String
@Throws(IOException::class)
fun readString(charset: Charset): String
...
fun inputStream(): InputStream
}
BufferedSink.kt
actual interface BufferedSink : Sink, WritableByteChannel {
actual val buffer: Buffer
// 寫(xiě)入 Int
@Throws(IOException::class)
actual fun writeInt(i: Int): BufferedSink
// 寫(xiě)入 String
@Throws(IOException::class)
fun writeString(string: String, charset: Charset): BufferedSink
...
fun outputStream(): OutputStream
}
2.4 RealBufferedSink 與 RealBufferedSource
BufferedSource 和 BufferedSink 還是接口,它們的真正的實(shí)現(xiàn)類(lèi)是 RealBufferedSource 和 RealBufferedSink坟比÷ⅲ可以看到,在實(shí)現(xiàn)類(lèi)中會(huì)創(chuàng)建一個(gè) Buffer 緩沖區(qū)葛账,在輸入和輸出的時(shí)候怜校,都會(huì)借助 “Buffer 緩沖區(qū)” 減少系統(tǒng)調(diào)用次數(shù)。
RealBufferedSource.kt
internal actual class RealBufferedSource actual constructor(
// 裝飾器模式
@JvmField actual val source: Source
) : BufferedSource {
// 創(chuàng)建輸入緩沖區(qū)
@JvmField val bufferField = Buffer()
// 帶緩沖地讀茸⒏汀(全部數(shù)據(jù))
override fun readString(charset: Charset): String {
buffer.writeAll(source)
return buffer.readString(charset)
}
// 帶緩沖地讀惹炎隆(byteCount)
override fun readString(byteCount: Long, charset: Charset): String {
require(byteCount)
return buffer.readString(byteCount, charset)
}
}
RealBufferedSink.kt
internal actual class RealBufferedSink actual constructor(
// 裝飾器模式
@JvmField actual val sink: Sink
) : BufferedSink {
// 創(chuàng)建輸出緩沖區(qū)
@JvmField val bufferField = Buffer()
// 帶緩沖地寫(xiě)入(全部數(shù)據(jù))
override fun writeString(string: String, charset: Charset): BufferedSink {
buffer.writeString(string, charset)
return emitCompleteSegments()
}
// 帶緩沖地寫(xiě)入(beginIndex - endIndex)
override fun writeString(
string: String,
beginIndex: Int,
endIndex: Int,
charset: Charset
): BufferedSink {
buffer.writeString(string, beginIndex, endIndex, charset)
return emitCompleteSegments()
}
}
至此,Okio 基本框架分析結(jié)束巩割,用一張圖總結(jié):
Okio 框架
3. Okio 的緩沖區(qū)設(shè)計(jì)
3.1 使用緩沖區(qū)減少系統(tǒng)調(diào)用次數(shù)
在操作系統(tǒng)中裙顽,訪(fǎng)問(wèn)磁盤(pán)和網(wǎng)卡等 IO 操作需要通過(guò)系統(tǒng)調(diào)用來(lái)執(zhí)行。系統(tǒng)調(diào)用本質(zhì)上是一種軟中斷宣谈,進(jìn)程會(huì)從用戶(hù)態(tài)陷入內(nèi)核態(tài)執(zhí)行中斷處理程序愈犹,完成 IO 操作后再?gòu)膬?nèi)核態(tài)切換回用戶(hù)態(tài)。
可以看到闻丑,系統(tǒng)調(diào)用存在上下文切換的性能損耗漩怎。為了減少系統(tǒng)調(diào)用次數(shù),應(yīng)用層往往會(huì)采用緩沖區(qū)策略:
以 Java 原生 IO BufferedInputStream
為例嗦嗡,會(huì)通過(guò)一個(gè) byte[] 數(shù)組作為數(shù)據(jù)源的輸入緩沖勋锤,每次讀取數(shù)據(jù)時(shí)會(huì)讀取更多數(shù)據(jù)到緩沖區(qū)中:
- 如果緩沖區(qū)中存在有效數(shù)據(jù),則直接從緩沖區(qū)數(shù)據(jù)讀冉募馈叁执;
- 如果緩沖區(qū)不存在有效數(shù)據(jù),則先執(zhí)行系統(tǒng)調(diào)用填充緩沖區(qū)(fill)矮冬,再?gòu)木彌_區(qū)讀取數(shù)據(jù)谈宛;
- 如果要讀取的數(shù)據(jù)量大于緩沖區(qū)容量,就會(huì)跳過(guò)緩沖區(qū)直接執(zhí)行系統(tǒng)調(diào)用胎署。
輸出流 BufferedOutputStream
也類(lèi)似吆录,輸出數(shù)據(jù)時(shí)會(huì)優(yōu)先寫(xiě)到緩沖區(qū),當(dāng)緩沖區(qū)滿(mǎn)或者手動(dòng)調(diào)用 flush() 時(shí)琼牧,再執(zhí)行系統(tǒng)調(diào)用寫(xiě)出數(shù)據(jù)恢筝。
偽代碼
// 1\. 輸入
fun read(byte[] dst, int len) : Int {
// 緩沖區(qū)有效數(shù)據(jù)量
int avail = count - pos
if(avail <= 0) {
if(len >= 緩沖區(qū)容量) {
// 直接從輸入流讀取
read(輸入流 in, dst, len)
}
// 填充緩沖區(qū)
fill(數(shù)據(jù)源 in, 緩沖區(qū))
}
// 本次讀取數(shù)據(jù)量哀卫,不超過(guò)可用容量
int cnt = (avail < len) ? avail : len?
read(緩沖區(qū), dst, cnt)
// 更新緩沖區(qū)索引
pos += cnt
return cnt
}
// 2\. 輸出
fun write(byte[] src, len) {
if(len > 緩沖區(qū)容量) {
// 先將緩沖區(qū)寫(xiě)出
flush(緩沖區(qū))
// 直接寫(xiě)出數(shù)據(jù)
write(輸出流 out, src, len)
}
// 緩沖區(qū)剩余容量
int left = 緩沖區(qū)容量 - count
if(len > 緩沖區(qū)剩余容量) {
// 先將緩沖區(qū)寫(xiě)出
flush(緩沖區(qū))
}
// 將數(shù)據(jù)寫(xiě)入緩沖區(qū)
write(緩沖區(qū), src, len)
// 更新緩沖區(qū)已添加數(shù)據(jù)容量
count += len
}
3.2 緩沖區(qū)的副作用
的確,緩沖區(qū)策略能有效地減少系統(tǒng)調(diào)用次數(shù)滋恬,不至于讀取一個(gè)字節(jié)都需要執(zhí)行一次系統(tǒng)調(diào)用,大多數(shù)情況下表現(xiàn)良好抱究。 但考慮一種 “雙流操作” 場(chǎng)景恢氯,即從一個(gè)輸入流讀取,再寫(xiě)入到一個(gè)輸出流鼓寺⊙猓回顧剛才講的緩存策略,此時(shí)的數(shù)據(jù)轉(zhuǎn)移過(guò)程為:
- 1妈候、從輸入流讀取到緩沖區(qū)敢靡;
- 2、從輸入流緩沖區(qū)拷貝到 byte[](拷貝)
- 3苦银、將 byte[] copy 到輸出流緩沖區(qū)(拷貝)啸胧;
- 4、將輸出流緩沖區(qū)寫(xiě)入到輸出流幔虏。
如果這兩個(gè)流都使用了緩沖區(qū)設(shè)計(jì)纺念,那么數(shù)據(jù)在這兩個(gè)內(nèi)存緩沖區(qū)之間相互拷貝,就顯得沒(méi)有必要想括。
3.3 Okio 的 Buffer 緩沖區(qū)
Okio 當(dāng)然也有緩沖區(qū)策略陷谱,如果沒(méi)有就會(huì)存在頻繁系統(tǒng)調(diào)用的問(wèn)題。
Buffer 是 RealBufferedSource 和 RealBufferedSink 的數(shù)據(jù)緩沖區(qū)瑟蜈。雖然在實(shí)現(xiàn)上與原生 BufferedInputStream 和 BufferedOutputStream 不一樣烟逊,但在功能上是一樣的。區(qū)別在于:
1铺根、BufferedInputStream 中的緩沖區(qū)是 “一個(gè)固定長(zhǎng)度的字節(jié)數(shù)組” 宪躯,數(shù)據(jù)從一個(gè)緩沖區(qū)轉(zhuǎn)移到另一個(gè)緩沖區(qū)需要拷貝;
2位迂、Buffer 中的緩沖區(qū)是 “一個(gè) Segment 雙向循環(huán)鏈表” 眷唉,每個(gè) Segment 對(duì)象是一小段字節(jié)數(shù)組,依靠 Segment 鏈表的順序組成邏輯上的連續(xù)數(shù)據(jù)囤官。這個(gè) Segment 片段是 Okio 高效的關(guān)鍵冬阳。
Buffer.kt
actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
// 緩沖區(qū)(Segment 雙向鏈表)
@JvmField internal actual var head: Segment? = null
// 緩沖區(qū)數(shù)據(jù)量
@get:JvmName("size")
actual var size: Long = 0L
internal set
override fun buffer() = this
actual override val buffer get() = this
}
對(duì)比 BufferedInputStream:
BufferedInputStream.java
public class BufferedInputStream extends FilterInputStream {
// 緩沖區(qū)的默認(rèn)大小(8KB)
private static int DEFAULT_BUFFER_SIZE = 8192;
// 輸入緩沖區(qū)(固定長(zhǎng)度的數(shù)組)
protected volatile byte buf[];
// 有效數(shù)據(jù)起始位党饮,也是讀數(shù)據(jù)的起始位
protected int pos;
// 有效數(shù)據(jù)量肝陪,pos + count 是寫(xiě)數(shù)據(jù)的起始位
protected int count;
...
}
3.4 Segment 片段與 SegmentPool 對(duì)象池
Segment 中的字節(jié)數(shù)組是可以 “共享” 的,當(dāng)數(shù)據(jù)從一個(gè)緩沖區(qū)轉(zhuǎn)移到另一個(gè)緩沖區(qū)時(shí)刑顺,可以共享數(shù)據(jù)引用氯窍,而不一定需要拷貝數(shù)據(jù)饲常。
Segment.kt
internal class Segment {
companion object {
// 片段的默認(rèn)大小(8KB)
const val SIZE = 8192
// 最小共享閾值狼讨,超過(guò) 1KB 的數(shù)據(jù)才會(huì)共享
const val SHARE_MINIMUM = 1024
}
// 底層數(shù)組
@JvmField val data: ByteArra
// 有效數(shù)據(jù)的起始位贝淤,也是讀數(shù)據(jù)的起始位
@JvmField var pos: Int = 0
// 有效數(shù)據(jù)的結(jié)束位,也是寫(xiě)數(shù)據(jù)的起始位
@JvmField var limit: Int = 0
// 共享標(biāo)記位
@JvmField var shared: Boolean = false
// 宿主標(biāo)記位
@JvmField var owner: Boolean = false
// 后續(xù)指針
@JvmField var next: Segment? = null
// 前驅(qū)指針
@JvmField var prev: Segment? = null
constructor() {
// 默認(rèn)構(gòu)造 8KB 數(shù)組(為什么默認(rèn)長(zhǎng)度是 8KB)
this.data = ByteArray(SIZE)
// 宿主標(biāo)記位
this.owner = true
// 共享標(biāo)記位
this.shared = false
}
}
另外政供,Segment 還使用了對(duì)象池設(shè)計(jì)播聪,被回收的 Segment 對(duì)象會(huì)緩存在 SegmentPool 中。SegmentPool 內(nèi)部維護(hù)了一個(gè)被回收的 Segment 對(duì)象單鏈表布隔,緩存容量的最大值是 MAX_SIZE = 64 * 1024
离陶,也就相當(dāng)于 8 個(gè)默認(rèn) Segment 的長(zhǎng)度:
SegmentPool.kt
// object:全局單例
internal actual object SegmentPool {
// 緩存容量
actual val MAX_SIZE = 64 * 1024
// 頭節(jié)點(diǎn)
private val LOCK = Segment(ByteArray(0), pos = 0, limit = 0, shared = false, owner = false)
...
}
Segment 示意圖
4. 總結(jié)
- 1、Okio 將原生 IO 多種基礎(chǔ)裝飾器聚合在 BufferedSource 和 BufferedSink衅檀,使得框架更加精簡(jiǎn)招刨;
- 2、為了減少系統(tǒng)調(diào)用次數(shù)的同時(shí)哀军,應(yīng)用層 IO 框架會(huì)使用緩存區(qū)設(shè)計(jì)沉眶。而 Okio 使用了基于共享 Segment 的緩沖區(qū)設(shè)計(jì),減少了在緩沖區(qū)間轉(zhuǎn)移數(shù)據(jù)的內(nèi)存拷貝杉适;
- 3沦寂、Okio 彌補(bǔ)了部分 IO 操作不支持超時(shí)檢測(cè)的缺陷,而且 Okio 不僅支持單次 IO 操作的超時(shí)檢測(cè)淘衙,還支持包含多次 IO 操作的復(fù)合任務(wù)超時(shí)檢測(cè)传藏。
關(guān)于 Okio 超時(shí)機(jī)制的詳細(xì)分析,我們?cè)?下一篇文章 里討論彤守。請(qǐng)關(guān)注毯侦。