Android IO 框架 Okio 的實(shí)現(xiàn)原理,到底哪里 OK吧碾?

前言

大家好凰盔,我是小彭。

今天倦春,我們來(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ì)使用 BufferedInputStreamBufferedOutputStream 包裝類(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ì)使用 BufferedSourceBufferedSink 包裝類(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)注毯侦。


作者:彭旭銳
鏈接:https://juejin.cn/post/7167757174502850597

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市具垫,隨后出現(xiàn)的幾起案子侈离,更是在濱河造成了極大的恐慌,老刑警劉巖筝蚕,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卦碾,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡起宽,警方通過(guò)查閱死者的電腦和手機(jī)洲胖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)坯沪,“玉大人绿映,你說(shuō)我怎么就攤上這事。” “怎么了叉弦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵丐一,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我淹冰,道長(zhǎng)库车,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任樱拴,我火速辦了婚禮柠衍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疹鳄。我一直安慰自己拧略,他們只是感情好芦岂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布瘪弓。 她就那樣靜靜地躺著,像睡著了一般禽最。 火紅的嫁衣襯著肌膚如雪腺怯。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天川无,我揣著相機(jī)與錄音呛占,去河邊找鬼。 笑死懦趋,一個(gè)胖子當(dāng)著我的面吹牛晾虑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仅叫,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼帜篇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了诫咱?” 一聲冷哼從身側(cè)響起笙隙,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坎缭,沒(méi)想到半個(gè)月后竟痰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掏呼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年坏快,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憎夷。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡假消,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出岭接,到底是詐尸還是另有隱情富拗,我是刑警寧澤臼予,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站啃沪,受9級(jí)特大地震影響粘拾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜创千,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一缰雇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧追驴,春花似錦械哟、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至丙曙,卻和暖如春爸业,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亏镰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工扯旷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人索抓。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓钧忽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親逼肯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子耸黑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容