okio
是Square开源框架之一,它对java.io
和java.nio
做了补充,使访问,存储和数据处理变得更加容易。它最早是Okhttp
组件之一。
1、ByteString与Buffer
Okio
主要围绕ByteString
与Buffer
这两个类展开,其主要功能都封装在这两个类中:
ByteString
:是一个类似String
的不可变类,它可以很容易的在byte
与String
之间进行转换。该类提供了编/解码为hex,md5,base64及UTF-8等方法。Buffer
:是一个可变的字节序列。 与ArrayList
一样,无需提前调整缓冲区大小。Buffer
内部维护了一个双向链表,从链表尾部写入数据,头部读取数据。
ByteString
和Buffer
做了一些节省CPU和内存的操作。 如果将一个字符串编码为ByteString
,ByteString
就会缓存对该字符串的引用(以空间换时间),这样如果以后对其进行编/解码等操作,则无需在byte
与String
之间进行转换。
//字符串对应的字节数据,避免再一次转换
final byte[] data;
//字符串
transient String utf8; // Lazily computed.
Buffer
内部维护了一个以Segment
为节点的双向链表。 当数据从一个Buffer
移动到另一个Buffer
时,仅需要进行一次数据拷贝,且它会重新分配Segment
的所有权,而不是重新创建Segment
对象。
2、Source与Sink
Okio
包含自己的流类型,称为Source
和Sink
,其工作方式虽然类似InputStream
和OutputStream
,但它与Java I/O相比具有以下优势(参考自Android学习笔记——Okio):
Okio
实现了I/O读写的超时机制(Timeout
),防止读写出错从而导致一直阻塞。- N合一,
OKio
精简了输入输出流的类个数 - 低的CPU和内存消耗,引入
Segment
和SegmentPool
复用机制 - 使用方便。
ByteString
处理不变byte
,Buffer
处理可变byte
。 - 提供了一系列的工具。
OKio
支持md5、sha、base64等数据处理
Source
、Sink
可以与InputStream
、OutputStream
互相操作。我们可以将任何Source
视为InputStream
,也可以将任何InputStream
视为Source
。同样适用于Sink
和InputStream
。
3、Okio数据读写流程
前面简单介绍了Okio
,下面就来看看如何使用。
//okio实现图片复制
public void copyImage(File sinkFile, File sourceFile) throws IOException {
//try里面的代码是Okio的标准写法,不能改变
try (Sink sink = Okio.sink(sinkFile);
BufferedSink bufferedSink = Okio.buffer(sink);
//从文件读取数据
Source source = Okio.source(sourceFile);
BufferedSource bufferedSource = Okio.buffer(source)) {
//图片复制
bufferedSink.write(bufferedSource.readByteArray());
//设置超时时间为1秒中,
sink.timeout().deadline(1, TimeUnit.SECONDS);
//写入数据,将字符串以UTF-8格式写入,Okio专门针对utf-8做了处理
bufferedSink.writeUtf8(entry.getKey())
.writeUtf8("=")
.writeUtf8(entry.getValue())
.writeUtf8("\n");
//读取数据
String str=bufferedSource.readUtf8();
//读取数据并返回一个ByteString
ByteStringstr=bufferedSource.readByteString();
}
}
正如前面所说的那样,Okio
使用起来非常方便。由于Java字符串采用的是UTF-16编码,而一般开发中使用的都是UTF-8编码,所以Okio
对字符串编码做了特殊处理。
3.1、Okio读数据原理分析
Source
的意思是水源,它对应着输入流,在Okio
中通过Okio.source
方法来获得一个Source
对象。
//在Okio这个类中关于source重载的方法还是蛮多的,这里以文件为例
public static Source source(File file) throws FileNotFoundException {
if (file == null) throw new IllegalArgumentException("file == null");
return source(new FileInputStream(file));
}
public static Source source(InputStream in) {
return source(in, new Timeout());
}
private static Source source(final InputStream in, final Timeout timeout) {
...
//这里才是真正读去数据的地方
return new Source() {
@Override public long read(Buffer sink, long byteCount) throws IOException {
...
try {
//每次写数据时都先检查是否超时,默认未设置超时
timeout.throwIfReached();
//获取链表的尾节点
Segment tail = sink.writableSegment(1);
//由于每个Segment的SIZE为8KB,所以每一次拷贝不能超过这个值
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
//通过InputStream读取数据
int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
//数据读取完毕
if (bytesRead == -1) return -1;
//可写取位置往后移
tail.limit += bytesRead;
//读取的总字节数
sink.size += bytesRead;
//返回当前读取的字节数
return bytesRead;
} catch (AssertionError e) {
...
}
}
...
};
}
可以发现,这个的Source
是一个匿名对象。得到Source
对象后,通过Okio.buffer
方法将该对象传递给BufferedSource
,BufferedSource
是一个接口,它的具体实现类是RealBufferedSource
。
在上面例子中是调用RealBufferedSource
的readByteArray
方法来读取数据,下面就来看这个方法的实现。
//RealBufferedSource对应的Buffer
public final Buffer buffer = new Buffer();
@Override public byte[] readByteArray() throws IOException {
//将数据写入buffer
buffer.writeAll(source);
//将所有数据已字节数组形式返回
return buffer.readByteArray();
}
在readByteArray
方法中会首先将数据写入到Buffer
中,并生成一个双向链表。
@Override public long writeAll(Source source) throws IOException {
if (source == null) throw new IllegalArgumentException("source == null");
long totalBytesRead = 0;
//这里的source就是前面在Okio中创建的匿名Source对象
for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) {
totalBytesRead += readCount;
}
return totalBytesRead;
}
将数据写入Buffer
后,调用Buffer
的readByteArray
方法生成一个字节数组并返回。
@Override
public byte[] readByteArray() {
try {
//在读取数据时,就会得到size的大小
return readByteArray(size);
} catch (EOFException e) {
throw new AssertionError(e);