okio 原理分析(一)

1 okio 概念

以下三小节翻译自 okio 文档

1.1 okio

okio 是对传统 io 和 nio 的扩展库,使数据访问、数据存储及相应处理更加简单。

1.2 ByteStrings 和 Buffers

Okio 是围绕 ByteStringBuffer 两种类型来构建的,ByteStringBuffer 提供了简明的 API,并包含了丰富的功能。

  • ByteString 是一个不可变(immutable)byte 序列。String 类型是字符数据的基础(String 是不可变的,比如不能在原变量中修改某个字符),而 ByteString 有点像 String,但它是用来简化处理二进制数据的。ByteString 是个‘人性化’的类:它知道如何将自身编码/解码成 hex,base64 和 UTF-8 类型的数据。
  • Buffer 是一个可变(mutable) byte 序列。就像 ArrayList,使用者不用提前设置 Buffer 的大小。读写 Buffer 时,可以把它想像成一个 Queue:把写入的数据放到了队尾,读取时从队头获取数据。使用者不用关心怎么去维护 positions, limits 或 capacities(译注:这些都是相对底层使用到的标记信息,Buffer 通过 Segment 对象对这些信息进行了封装)。

ByteStringBuffer 内部做了一些聪明的事件来节省 CUP 和 内存资源。比如,使用者把一个 UTF-8 类型的 String 编码为一个 ByteStringByteString 会缓存这个 String 的引用,之后对 ByteString 解码时就不需要执行解码操作了。

Buffer 内部有一个 Segment 的链表。当需要将数据从一个 buffer 转移到另一个 buffer 时,Buffer 会改变 Segment 的所属对象,这样就避免了来回拷贝数据。这种处理方式在多线程环境下特别好使:一个用于网络传输的线程可以将数据直接传送给工作线程而且不需要任何复制或仪式性(ceremony)的操作。

1.3 Sources 和 Sinks

java.io 设计优雅的部分是将流(streams)进行分层,一层一层对数据流进行传输处理,比如加密和压缩。Okio 包含了另外的一套流类型叫做 SourceSink(译注:这种命名来自 guava),它们工作机制与 InputStreamOutputStream 类似,但有如下主要区别:

  • 超时。 okio 流对基础 I/O 操作提供了访问超时机制。不像 java.io socket 流,okio 中 read()write() 都支持超时功能。
  • 容易实现(implement)。 Source 声明了三个方法:read(), close()timeout()。没有需要靠人品方法如 available() 或单字节读取操作,单字节读取会导致程序在正确性和性能方面时有‘惊喜’。
  • 容易使用。 尽管 SourceSink 只有三个方法去实现(implements),调用者通过 BufferedSourceBufferedSink 提供了丰富的 API,这里有你需要的一切。
  • 形式上不区分字节流和字符流。 它们全部是 data。读取和写入时会把 data 当作 byte, UTF-8 字符串,大端 32-bit 整型,小端 short 型;无论做什么,都不会再有 InputStreamReader!
  • 容易测试。 Buffer 类实现了 BufferedSourceBufferedSink 接口,so 你的测试代码会简单清晰。

Source 和 Sink 可以与 InputStreamOutputStream 相互转换(译注:通过 okio 提供的工具方法和实现相关接口来转换)。可以将任何 Source 视作 InputStream,并且可以把任何 InputStream 当作 Source。类似的,SinkOutputStream 也有这样的关系。

2 底层实现

本节会从原理及源码角度去分析 okio 的诸多优点,看看别人家的代码是如何写的…

2.1 简洁明了 API 背后的逻辑

还记得被 java.io 各种字节流、字符流处理支配的恐惧吗?还记得为了记住 InputStreamReaderOutputStreamWriter 是干什么的而精神分裂吗?是的,这些东西在 okio 里统统没有。okio 里不分字节流、字符流,甚至没有‘流’的概念。它在处理数据的方面更偏向与 nio 中‘块’的概念,在 okio 中叫 SegmentSegment 是以队列(双向链表)形式存在的,并且 Segment 的创建与销毁共享同一个缓存池 – SegmentPool。后面会分析 SegmentSegmentPool 的工作原理。现在先体会一下它简洁的 API,ok, 帖代码,看看 okio 是如何完成文件复制的

@Test
public void readAndSaveFileByOkio() throws Exception {
   
    final int size = 1024;
    // createTemp(size) 是自定义方法,表示创建一个长度等于 size 字节的文件
    File srcFile = createTemp(size);
    assertTrue(srcFile.exists());

    File desFile = temporaryFolder.newFile();
    assertEquals(desFile.length(), 0L);

    BufferedSource source = Okio.buffer(Okio.source(srcFile)); // 1
    BufferedSink sink = Okio.buffer(Okio.sink(desFile)); // 2

    source.readAll(sink); // 3

    source.close();
    sink.close();

    assertEquals(desFile.length(), size);
}

从整个过程来看,只用三行代码完成了数据的转移。这里主要涉及了三个概念 Source, Sink 和 Buffer,Source 代表了数据来源,Sink 代表数据要传输到何方,而 Buffer 就是数据的中转站。它们关系如下:
okio structure

细分 1、2、3 行代码,可以感觉到第 1、2 行属于构建行为,描绘出数据流动趋势,真正动作的发起者是 source.readAll(sink)。现在以 source.readAll(sink) 方法为主线,看一下 okio 是如何运行的。

首先看一下 source 生成过程,通过工具方法 Okio.buffer(Source) 生成一个 RealBufferedSource 对象。

public static BufferedSource buffer(Source source) {
   
    return new RealBufferedSource(source);
}

看名字就能猜出对 Source 所有操作会在 RealBufferedSource 对象内进行,暂时不需要知道 source 是什么,等到查看 Source 某个方法需要调用到派生类中的方法时再看。同样 Okio.buffer(Sink) 会返回一个 RealBufferedSink 对象。

进入 source.readAll(sink) 方法,即 RealBufferedSource.readAll(Sink) 方法

// RealBufferedSource.readAll
@Override 
public long readAll(Sink sink) throws IOException {
   
    ...
    long totalBytesWritten = 0;
    // 从 source 读取 SIZE 份数据,放入 buffer 中,直到读取值为 -1
    while (source.read(buffer, Segment.SIZE) != -1) {
   
        long emitByteCount = buffer.completeSegmentByteCount();
        if (emitByteCount > 0) {
   
            totalBytesWritten += emitByteCount;
            // 向 sink 中写入 emitByteCount 份数据
            sink.write(buffer, emitByteCount);
        }
    }
    ...
    return totalBytesWritten;
}

里面代码也挺直白的,因为 source 是个接口,所以要定位 source.read(buffer, Segment.SIZE) 的执行者,现在可以看 Okio.source(srcFile) 方法了

// Okio.source
public static Source source(File file) throws FileNotFoundException {
   
    ...
    return source(new FileInputStream(file));
}

// Okio.source
public static Source source(InputStream in) {
   
    return source(in, new Timeout());
}

// Okio.source
private static Source source(final InputStream in, 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值