Okio 之初探黄龙
Okio 是一个包装了 java.io
和 java.nio
api 的库,以便可以更容易的访问、存储以及处理数据。
ByteStrings 和 Buffers
Okio 是围绕着两个容器类构建起来的:
ByteString
是一个不可变的字节序列。对于字符数据,Java 中的String
类提供了基本的支持。ByteString
就像是 String 失散多年的兄弟,对二进制数据提供了基本的支持。这个类是非常人性化的:它明白如何编码或解码自身来作为十六进制字符串、base64 字符串或者 UTF-8 字符串。Buffer
是一个可变的字节序列。就像ArrayList
一样,你不需要提前规定你的buffer
的大小。你可以像读写队列一样读写buffer
:写数据到末尾、从头部读数据。你不需要负责管理positions
,limits
, 以及capacities
。
ByteString
和 Buffer
内部做了一些优化以便节省 CPU 和内存资源。如果你把一个 UTF-8 的字符串转换为 ByteString
,ByteString
会缓存这个字符串的引用,之后如果你在解码时就直接返回这个字符串引用即可,无需做什么额外的工作。
Buffer
是由段(segment)链表组成的。当你从一个 Buffer
移动数据到另外一个 Buffer
时,它只是重新设置了段(segment)的拥有者而不是复制数据。这种方法对于多线程程序将是非常有用的:网络线程可以和工作者线程交换数据而无需任何的复制。
Sources 和 Sinks
java.io
原生 API 设计精妙的一处就是流数据可以分层进行转换处理,比如:加密和压缩。就像 InputStream
和 OutputStream
,Okio 也包含了自己的流类型 Source
和 Sink
,但是却有几点关键性的不同之处:
- 超时机制。Okio 的流类型对底层 I/O 访问时增加了超时机制,而
java.io
套接字流的read()
和write()
方法均是永不超时的。 - 实现简单。
Source
类声明了三个方法:read()
,close()
, 和timeout()
,不存在像available()
这种危险的方法或者单字节读取数据这种容易造成性能问题的方法。 - 容易使用。尽管
Source
和Sink
只有三个方法来读写数据,但是调用者可以使用功能丰富的实现类BufferedSource
和BufferedSink
接口。 - 对于字节流和字符流没有人工区别。Okio 中字节流和字符流都是一样的,可以以字节流,UTF-8 字符串,大端 32位整数,小端短整数来读写。不再有
InputStreamReader
。 - 容易测试。
Buffer
类实现了BufferedSource
和BufferedSink
接口,所以你的测试代码将是简单清晰的。
Sources 加 sinks 和 InputStream
加 OutputStream
内部是可以互相操作的。所以你可以把 Source
看做一个 InputStream
,并且也可以把 InputStream
看做一个 Source
;Sink
和 OutputStream
同样如此。
示例:一个 PNG 解码器
package okio;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by clearbug on 2017/11/5.
*/
public class PngDecoder {
private static final ByteString PNG_HEADER = ByteString.decodeHex("89504e470d0a1a0a");
public static void main(String[] args) throws IOException {
InputStream inputStream = new FileInputStream("/Users/clearbug/Pictures/Small-mario.png");
PngDecoder pngDecoder = new PngDecoder();
pngDecoder.decodePng(inputStream);
}
public void decodePng(InputStream in) throws IOException {
try (BufferedSource pngSource = Okio.buffer(Okio.source(in))) {
ByteString header = pngSource.readByteString(PNG_HEADER.size());
if (!header.equals(PNG_HEADER)) {
throw new IOException("Not a PNG.");
}
while (true) {
Buffer chunk = new Buffer();
// Each chunk is a length, type, data, and CRC offset.
int length = pngSource.readInt();
String type = pngSource.readUtf8(4);
pngSource.readFully(chunk, length);
int crc = pngSource.readInt();
decodeChunk(type, chunk);
if (type.equals("IEND")) break;
}
}
}
private void decodeChunk(String type, Buffer chunk) {
if (type.equals("IHDR")) {
int width = chunk.readInt();
int height = chunk.readInt();
System.out.printf("%08x: %s %d x %d%n", chunk.size(), type, width, height);
} else {
System.out.printf("%08x: %s%n", chunk.size(), type);
}
}
}