大家好,又见面了,我是你们的朋友全栈君。
一.Okio是什么
文档介绍地址:https://square.github.io/okio/
github地址:https://github.com/square/okio
Okio是java.io和java.nio的一个补充库,使访问、存储和处理数据更加容易。
包含两部分:ByteStrings 和 Buffers
BysteString:是一个不可变的字节序列,可以看做Sring丢失已久的兄弟。它很容的将字节编码或解码为hex、base64 和UTF-8;
Buffer:可变的字节序列,像ArrayList一样,不需要一些设置大小。
二.Okio实现过程分析
Okio的主要类结构如下,主要工作是为了更好的处理IO操作
Sink 文件写入操作实现:
以下代码基于okio:1.17.2版本进行分析
下面先看一个简单的代码示例,使用Sink进行写操作
private void testWrite() throws IOException {
File file = getTargetFile();
try(BufferedSink sink = Okio.buffer(Okio.sink(file))) {
for(Map.Entry<String,String> entry : System.getenv().entrySet()){
sink.writeUtf8(entry.getValue())
.writeUtf8("=")
.writeUtf8(entry.getValue())
.writeUtf8("\n");
}
sink.writeUtf8("").writeUtf8("\n");
}
}
第一步:创建sink对象;根据传入的File创建FileOutputStream(),再创建Sink对象,实现具体的写入操作
public static Sink sink(File file) throws FileNotFoundException {
if (file == null) throw new IllegalArgumentException("file == null");
return sink(new FileOutputStream(file));
}
//使用java的 OutputStream完成最终的数据写入操作
private static Sink sink(final OutputStream out, final Timeout timeout) {
if (out == null) throw new IllegalArgumentException("out == null");
if (timeout == null) throw new IllegalArgumentException("timeout == null");
return new Sink() {
@Override public void write(Buffer source, long byteCount) throws IOException {
checkOffsetAndCount(source.size, 0, byteCount);
while (byteCount > 0) {
timeout.throwIfReached();
//取出buffer的segment
Segment head = source.head;
int toCopy = (int) Math.min(byteCount, head.limit - head.pos);
out.write(head.data, head.pos, toCopy);
//改变head的使用位置
head.pos += toCopy;
byteCount -= toCopy;
source.size -= toCopy;
//一个segment使用完成,回收到SegmentPool
if (head.pos == head.limit) {
source.head = head.pop();
SegmentPool.recycle(head);
}
}
}
@Override public void flush() throws IOException {
out.flush();
}
......
};
}
第二步:创建BufferedSink对象,BufferedSink是一个抽象类,具体的实现有RealBufferedSink完成
BufferedSink sink = Okio.buffer(Okio.sink(file));
//RealBufferedSink是具体的实现类,
public static BufferedSink buffer(Sink sink) {
return new RealBufferedSink(sink);
}
final class RealBufferedSink implements BufferedSink {
public final Buffer buffer = new Buffer();
public final Sink sink;
boolean closed;
RealBufferedSink(Sink sink) {
if (sink == null) throw new NullPointerException("sink == null");
this.sink = sink;
}
...
@Override public BufferedSink writeUtf8(String string) throws IOException {
if (closed) throw new IllegalStateException("closed");
//向Segment写入数据由Buffer完成
buffer.writeUtf8(string);
return emitCompleteSegments();
}
@Override public BufferedSink emitCompleteSegments() throws IOException {
if (closed) throw new IllegalStateException("closed");
long byteCount = buffer.completeSegmentByteCount();
//这里调用okio创建的Sink对象的sink方法,由OutputStream写入数据
//先将数据写入到Buffer,再由具体的实现执行写入操作,这样更易扩展
if (byteCount > 0) sink.write(buffer, byteCount);
return this;
}
...
}
第三步:Buffered的写入数据操作,写入数据完成后,调用RealBufferedSink的emitCompleteSegments()方法,将具体的数据写入文件中
@Override public Buffer writeUtf8(String string, int beginIndex, int endIndex) {
......
// Transcode a UTF-16 Java String to UTF-8 bytes.
for (int i = beginIndex; i < endIndex;) {
int c = string.charAt(i);
if (c < 0x80) {
//获得一个可以写入的Segment
Segment tail = writableSegment(1);
......
} else if (c < 0x800) {
// Emit a 11-bit character with 2 bytes.
......
} else if (c < 0xd800 || c > 0xdfff) {
// Emit a 16-bit character with 3 bytes.
......
} else {
.....
}
}
整个流程和平时我们操作不同的地方在于首先将数据写入到Buffer,最后再由实际的FileOutputStream写入到文件。
这里Segment的使用使用说明一下:
final class Segment{
static final int SIZE = 8192;
final byte[] data;
//下一个可以读取的位置
/** The next byte of application data byte to read in this segment. */
int pos;
//下一个可以写入的位置
/** The first byte of available data ready to be written to. */
int limit;
......
......
}
limit每次写入数据后,会像后移动对应的写入大小,pos每次读取数据后,向后移动对应读取数据大小,第一次写入8个byte后位置如下图:
当读取2个byte后位置如下图:
Source 的实现过程
三.Okio如何做到性能更优
空间换时间
使用SegmentPool缓存Segment,同时也是为了缓存byte[],减少频繁的创建byte[]数组导致gc,引起内存抖动,影响体验。
在内部,ByteStrig和Buffer做了一些事来节省CPU和内存,
Buffer以分段的链表实现,当数据从一个buffer移动到另一个buffer的时,他会重新分配segment的所有权,而不是跨段复制数据,这种方法对于多线程程序特别有用,一个网络线程可以和工作线程进行交互数据,而不需要任何的拷贝
Sources and Sinks
Souce 对应 InputStream,Sink 对应 OutputStream。但是有以下方面的不同:
- Timeouts. 该流提供了底层的访问超市机制。对于java.io的 socket流,read()和write()都调timeouts.
- 易于实现.的ouce 申明了三个方法,read(),close()和timeout()。没有像available()或单字节读取这样的危险会导致正确性和性能上的意外。
- 易使用.可使用BufferdSouce和BufferedSink提供的丰富API满足日常需求。BufferedSource使用了缓存,能够在更少的I/O情况下,做更多的事情。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/134674.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...