返回
顶部

修改密码

首页 > 文章 > 国内 > 正文
Android IO 框架 Okio 的实现原理,到底哪里 OK

+1

-1

收藏

+1

-1

点赞0

评论0

标题:Android IO 框架 Okio 的实现原理,到底哪里 OK
详情介绍-作者:xiaoyi-来源: 极全网 -如有问题点击:在线客服帮助

今天,我们来讨论一个 Square 开源的 I/O 框架 Okio,我们最开始接触到 Okio 框架还是源于 Square 家的 OkHttp 网络框架。那么,OkHttp 为什么要使用 Okio,它相比于 Java 原生 IO 有什么区别和优势?今天我们就围绕这些问题展开。

本文源码基于 Okio v3.2.0。

1. 说一下 Okio 的优势?

相比于 Java 原生 IO 框架,我认为 Okio 的优势主要体现在 3 个方面:

  • 1、精简且全面的 API: 原生 IO 使用装饰模式,例如使用 BufferedInputStream 装饰 FileInputStream 文件输入流,可以增强流的缓冲功能。但是原生 IO 的装饰器过于庞大,需要区分字节、字符流、字节数组、字符数组、缓冲等多种装饰器,而这些恰恰又是最常用的基础装饰器。相较之下,Okio 直接在 BufferedSource 和 BufferedSink 中聚合了原生 IO 中所有基础的装饰器,使得框架更加精简;

  • 2、基于共享的缓冲区设计: 由于 IO 系统调用存在上下文切换的性能损耗,为了减少系统调用次数,应用层往往会采用缓冲区策略。但是缓冲区又会存在副作用,当数据从一个缓冲区转移到另一个缓冲区时需要拷贝数据,这种内存中的拷贝显得没有必要。而 Okio 采用了基于共享的缓冲区设计,在缓冲区间转移数据只是共享 Segment 的引用,而减少了内存拷贝。同时 Segment 也采用了对象池设计,减少了内存分配和回收的开销;

  • 3、超时机制: Okio 弥补了部分 IO 操作不支持超时检测的缺陷,而且 Okio 不仅支持单次 IO 操作的超时检测,还支持包含多次 IO 操作的复合任务超时检测。

下面,我们将从这三个优势展开分析:

2. 精简的 Okio 框架

先用一个表格总结 Okio 框架中主要的类型:

类型描述
Source输入流
Sink输出流
BufferedSource缓存输入流接口,实现类是 RealBufferedSource
BufferedSink缓冲输出流接口,实现类是 RealBufferedSink
Buffer缓冲区,由 Segment 链表组成
Segment数据片段,多个片段组成逻辑上连续数据
ByteStringString 类
Timeout超时控制

2.1 Source 输入流 与 Sink 输出流

在 Java 原生 IO 中有四个基础接口,分别是:

  • 字节流: InputStream 输入流和 OutputStream 输出流;

  • 字符流: Reader 输入流和 Writer 输出流。

而在 Okio 更加精简,只有两个基础接口,分别是:

  • 流: Source 输入流和 Sink 输出流。

Source.kt

interface Source : Closeable {

    // 从输入流读取数据到 Buffer 中(Buffer 等价于 byte[] 字节数组)
    // 返回值:-1:输入内容结束
    @Throws(IOException::class)
    fun read(sink: Buffer, byteCount: Long): Long    // 超时控制(详细分析见后续文章)
    fun timeout(): Timeout    // 关闭流
    @Throws(IOException::class)
    override fun close()}

Sink.java

actual interface Sink : Closeable, Flushable {

    // 将 Buffer 的数据写入到输出流中(Buffer 等价于 byte[] 字节数组)
    @Throws(IOException::class)
    actual fun write(source: Buffer, byteCount: Long)

    // 清空输出缓冲区
    @Throws(IOException::class)
    actual override fun flush()

    // 超时控制(详细分析见后续文章)
    actual fun timeout(): Timeout    // 关闭流
    @Throws(IOException::class)
    actual override fun close()}

2.2 InputStream / OutputStream 与 Source / Sink 互转

在功能上,InputStream - Source 和 OutputStream - Sink 分别是等价的,而且是相互兼容的。结合 Kotlin 扩展函数,两种接口之间的转换会非常方便:

  • source(): InputStream 转 Source,实现类是 InputStreamSource;

  • sink(): OutputStream 转 Sink,实现类是 OutputStreamSink;

比较不理解的是: Okio 没有提供 InputStreamSource 和 OutputStreamSink 转回 InputStream 和 OutputStream 的方法,而是需要先转换为 BufferSource 与 BufferSink,再转回 InputStream 和 OutputStream。

  • buffer(): Source 转 BufferedSource,Sink 转 BufferedSink,实现类分别是 RealBufferedSource 和 RealBufferedSink。

示例代码

// 原生 IO -> Okioval source = FileInputStream(File("")).source()val bufferSource = FileInputStream(File("")).source().buffer()val sink = FileOutputStream(File("")).sink()val bufferSink = FileOutputStream(File("")).sink().buffer()// Okio -> 原生 IOval inputStream = bufferSource.inputStream()val outputStream = bufferSink.outputStream()

JvmOkio.kt

// InputStream -> Sourcefun InputStream.source(): Source = InputStreamSource(this, Timeout())// OutputStream -> Sinkfun 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 {
            // 同步超时监控(详细分析见后续文章)
            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        // 写出 Buffer
        while (remaining > 0) {
            // 同步超时监控(详细分析见后续文章)
            timeout.throwIfReached()
            // 取有效数据量和剩余输出量的较小值
            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            // 指向下一个 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 -> BufferedSourcefun Source.buffer(): BufferedSource = RealBufferedSource(this)// Sink -> BufferedSinkfun Sink.buffer(): BufferedSink = RealBufferedSink(this)

2.3 BufferSource 与 BufferSink

在 Java 原生 IO 中,为了减少系统调用次数,我们一般不会直接调用 InputStream 和 OutputStream,而是会使用 BufferedInputStreamBufferedOutputStream 包装类增加缓冲功能。

例如,我们希望采用带缓冲的方式读取字符格式的文件,则需要先将文件输入流包装为字符流,再包装为缓冲流:

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
极全网
版权声明:本文内容由极全网实名注册用户自发贡献,版权归原作者所有,极全网-官网不拥有其著作权,亦不承担相应法律责任。具体规则请查看《极全网用户服务协议》和《极全网知识产权保护指引》。如果您发现极全网中有涉嫌抄袭的内容,点击进入填写侵权投诉表单进行举报,一经查实,极全网将立刻删除涉嫌侵权内容。

扫一扫在手机打开

评论
已有0条评论
0/150
提交
热门评论
相关推荐
换一批
热点排行