烦恼一般都是想太多了。

0%

Okio的概念和使用

从 Retrofit/OkHttp 中的依赖而来,其封装 的一些IO操作,其里面有两个关键的概念 source, sink ,这两个概念我在 Lua 中也看到,典型的是 ltn12,source 是源, sink 是终点的意思,可以将其看做是输出。

Okio 的项目地址

ByteStrings and Buffers

Okio 围绕两种类型:ByteStrings 与 Buffer 来构造,将很多的能力都封装到了简单的API中。

  • ByteStrings 不可变的字节序列。对于字符数据,String 是基础。ByteString 对 String 进行了提升,使其更容易将二进制数据当做值来对待。
  • Buffer 是一个可变的字节序列。就如何 ArrayList,我们不用控制我们的 buffer 的大小。我们不需要担心读取的位置,限制及容量。

Buffer 被实现为很多段的链表。当我们从一个 buffer 将数据移动到另一个的时候,不会进行数据的复制,而只会将段的引用进行增加。

Sources and Sinks

类似 InputStream 和 OutStream,但有一些关键的不同,Sources and Sinks 会与 InputStream, OutStream 进行交互。

source 与 sink 都很简单,只有三个方法:

public interface Source extends Closeable {
/**
* Removes at least 1, and up to {@code byteCount} bytes from this and appends
* them to {@code sink}. Returns the number of bytes read, or -1 if this
* source is exhausted.
*/
long read(Buffer sink, long byteCount) throws IOException;

/** Returns the timeout for this source. */
Timeout timeout();

/**
* Closes this source and releases the resources held by this source. It is an
* error to read a closed source. It is safe to close a source more than once.
*/
@Override void close() throws IOException;
}

public interface Sink extends Closeable, Flushable {
/** Removes {@code byteCount} bytes from {@code source} and appends them to this. */
void write(Buffer source, long byteCount) throws IOException;

/** Pushes all buffered bytes to their final destination. */
@Override void flush() throws IOException;

/** Returns the timeout for this sink. */
Timeout timeout();

/**
* Pushes all buffered bytes to their final destination and releases the
* resources held by this sink. It is an error to write a closed sink. It is
* safe to close a sink more than once.
*/
@Override void close() throws IOException;
}

可以看到, source 与 sink 都是在 Buffer 上进行操作的,所以,这是IO间进行交互的桥梁。

BufferedSink BufferedSource

一般来说,一个 InputStream OutputStream 上的数据是位于内核空间的。我们都需要将其读取到用户空间后才能进行操作。如果我们不进行缓冲,那么势必造成用户空间的内存暴涨,甚至造成 OOM,所以对于 source 和 sink 都有一个子类,通过缓冲区来进行操作。我们以 BufferedSource 为例来看,其实现类是RealBufferedSource。

read()

@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");

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);
}

我们看到,在读取的时候,会首先从缓冲区读取 Segment.SIZE(8192) 字节的数据到缓冲区,然后再从缓冲区读取到 sink 去。也就是,一次最多可以从 BufferedSource 读取 8192 字节。那么如果我要一次性读完可以不呢?是可以的。

readAll()

BufferedSource 的 readAll 方法提供了这点:

@Override public long readAll(Sink sink) throws IOException {
if (sink == null) throw new IllegalArgumentException("sink == null");

long totalBytesWritten = 0;
while (source.read(buffer, Segment.SIZE) != -1) {
long emitByteCount = buffer.completeSegmentByteCount();
if (emitByteCount > 0) {
totalBytesWritten += emitByteCount;
sink.write(buffer, emitByteCount);
}
}
if (buffer.size() > 0) {
totalBytesWritten += buffer.size();
sink.write(buffer, buffer.size());
}
return totalBytesWritten;
}

可以看到,都是通过每 Segment.SIZE 一次进行读取,然后再写出的。对于 BufferedSink 也是一样,也是通过缓冲区进行逐次写入。