作者简介:ASCE1885, 《Android 高级进阶》作者。
本文由于潜在的商业目的,未经授权不开放全文转载许可,谢谢!
本文分析的源码版本已经 fork 到我的 Github。
前文中我们了解了 Java I/O 的历史和 okio 的基本概念,本文将开始 okio 中的输入流 Source 的剖析,首先看下它的类结构图:
可以看到,层次还是挺丰富的,平时用的最多的自然是 Source --> BufferedSource --> RealBufferedSource
和 Buffer
。既然是 Source 专场,那么我们就先来聊聊 Source 的基本概念。
注意这里的 Source 是一个统称的概念,并不是指上图中的 Source 这个接口。
Source 是对输入字节流的封装,通过它提供的接口,我们可以从网络,本地存储或者内存缓存中读取数据,同时它也可以提供对数据的转换操作,例如解压,解密等。和 java.io
的 InputStream 相比,两者实现的功能其实是一样的。但 InputStream 处理异构数据时需要多种输入流,例如使用 DataInputStream 处理基本数据类型的数据, 使用 BufferedInputStream 增加缓存能力,使用 InputStreamReader 用来读取字符串数据等,而 Source 体系中的 BufferedSource 能够满足所有这些需求。接下来我们就来细细品味 Source 体系中的各个成员,首先从 Source 接口开始。
Source 接口
Source 是输入流,存在需要关闭的数据,因此,Source 接口继承了 Closeable 并在 close
方法中关闭资源。同时定义了从 Buffer 中读取指定个数的字节信息的 read
方法,相比 InputStream,Source 接口定义了 timeout
方法实现超时机制,代码如下所示:
public interface Source extends Closeable {
long read(Buffer sink, long byteCount) throws IOException;
Timeout timeout();
@Override void close() throws IOException;
}
BufferedSource 接口
BufferedSource 接口在 Source 接口的基础上,主要做了两件事:
- 利用内部定义的缓冲区 Buffer 来提高数据读取的性能
- 定义一系列读取基本数据类型和字符串类型的方法,例如 readByte 读取一个字节的信息,readShort 读取两个字节的信息,readInt 读取四个字节的信息,readLong 读取八个字节的信息,readByteString 读取字节信息并转换为 ByteString 类型,readByteArray 读取字节数组信息,readUtf8 读取字节信息并转换为 UTF-8 编码的字符串等。
BufferedSource 作为接口,具体的实现在它的实现类 RealBufferedSource 类和 Buffer 类中。这里需要说明一下,RealBufferedSource 内部也是使用 Buffer 类提供的接口来实现底层的数据读取等操作,我们可以将 RealBufferedSource 当作对 Buffer 类的一个代理,它在 Buffer 类提供的方法基础上增加一些校验等逻辑。这三者的关系如下图所示:
RealBufferedSource 类
既然 RealBufferedSource 类是代理类,那么可以预知它本身的代码逻辑不会很复杂,例如在实现 Source 接口提供的 read 方法时,它首先对入参进行校验,当参数为 null 或者不符合逻辑时抛出相应的异常,接着确保内部缓冲区 buffer 中有数据,然后根据缓存区 buffer 中的字节数和 read 方法想读取的字节数决定最终可以读取的字节数,最后调用 Buffer 类的实现的 Source 接口的同名方法,代码如下所示:
/**
* 将数据从输入流 source 中读取到缓冲区 buffer,然后经过 buffer 将数据写入输出流 sink 中
*/
@Override public long read(Buffer sink, long byteCount) throws IOException {
// 入参校验
if (sink == null) throw new IllegalArgumentException("sink == null");
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
// 确保缓存区 buffer 中有数据,一次最多从 source 中读取一个 Segment 大小的数据块
if (buffer.size == 0) {
long read = source.read(buffer, Segment.SIZE);
if (read == -1) return -1;
}
// 决定最终可以读取的字节大小
long toRead = Math.min(byteCount, buffer.size);
return buffer.read(sink, toRead);
}
这个类中其他方法也都是类似的校验逻辑,我们就不细说了,但有一段代码除外,那就是将 Source 转换为 InputStream 的逻辑,它位于 inputStream 方法中,这个方法也是实现自 BufferedSource 接口的。那么如何实现 Source 和 InputStream 的转换呢?由于 Source 是 okio 自身定义的概念,因此,想要转换成 InputStream 也就只能用最直接的方法,就是创建一个 InputStream,我们知道 InputStream 是一个抽象类,那么继承这个抽象类并实现它的抽象方法就可以了,当然,也可以用匿名内部类的方式,RealBufferedSource 中的 inputStream 方法就是使用匿名内部类的方式,也就是直接 new 出一个 InputStream,并实现它的抽象方法和重写部分方法,代码实现中,依然主要是作校验操作,具体数据读操作交由 Buffer 类。