背景
网络在Android中的重要性那是自然的,Android的HttpUrlconnection和Apach的HttpClientokio已经不能够满足Android的需求,Okio作为okhttp的io组件整个项目的规模不是很大,大神代码的质量也很高,是学习源码的良好素材,也为学习okhhtp奠定一些基础。
okio简单使用
1.1 okio的几种使用
代码开源项目,作者的测试源代码,使用了junit;
public final class OkioTest {
@Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
/**一.简单的文件读写*/
@Test public void readWriteFile() throws Exception {
//1.文件
File file = temporaryFolder.newFile();
//2.构建写缓冲池
BufferedSink sink = Okio.buffer(Okio.sink(file));
//3.向缓冲池写入文本
sink.writeUtf8("Hello, java.io file!");
//4.关闭缓冲池
sink.close();
/****************割*************************/
//1.构建读文件缓冲源
BufferedSource source = Okio.buffer(Okio.source(file));
//2.读文件
source.readUtf8();
//3.关闭缓冲源
source.close();
}
/****************二.文件内容的追*************/
@Test public void appendFile() throws Exception {
File file = temporaryFolder.newFile();
//1.将文件读入,并构建写缓冲池
BufferedSink sink = Okio.buffer(Okio.appendingSink(file));
//2.追加文本
sink.writeUtf8("Hello, ");
//3.关闭
sink.close();
assertTrue(file.exists());
assertEquals(7, file.length());
//4.再次追加文本,需要重新构建缓冲池对象
sink = Okio.buffer(Okio.appendingSink(file));
//5.追加文本
sink.writeUtf8("java.io file!");
//6.关闭缓冲池
sink.close();
}
/**************三.通过路径来读写文件****************/
@Test public void readWritePath() throws Exception {
Path path = temporaryFolder.newFile().toPath();
//1.构建写缓冲池
BufferedSink sink = Okio.buffer(Okio.sink(path));
//2.写缓冲
sink.writeUtf8("Hello, java.nio file!");
//3.关闭缓冲
sink.close();
//1.构建读缓冲源
BufferedSource source = Okio.buffer(Okio.source(path));
//2.读文本
source.readUtf8();
//3.关闭缓冲源
source.close();
}
/********四,写buffer********************/
//在okio中buffer是一个很重要的对象,在后面我们在详细介绍。
@Test public void sinkFromOutputStream() throws Exception {
//1.构建buffer对象
Buffer data = new Buffer();
//2.向缓冲中写入文本
data.writeUtf8("a");
//3.可以连续追加,类似StringBuffer
data.writeUtf8(repeat('b', 9998));
data.writeUtf8("c");
//4.构建字节数组流对象
ByteArrayOutputStream out = new ByteArrayOutputStream();
//5.构建写缓冲池
Sink sink = Okio.sink(out);
//6.向池中写入buffer
sink.write(data, 3);
}
/**********五.读buffer***********/
@Test public void sourceFromInputStream()
throws Exception {
//1.构建字节数组流
InputStream in = new ByteArrayInputStream(
("a" + repeat('b', Segment.SIZE * 2) + "c").getBytes(UTF_8));
// Source: ab...bc
//2.缓冲源
Source source = Okio.source(in);
//3.buffer
Buffer sink = new Buffer();
//4.将数据读入buffer
sink.readUtf8(3);
}
}
1.2okio的优势
可以看到,okio的文件读写操作,使用起来是很简单的,减少了很多io操作的基本代码,并且对内存和cpu使用做了优化(代码直观当然是看不出来的,okio作者在buffer类中有详细的说明,后面我们在分析这个类);
okio框架结构与源码分析
1.Okio框架分析
Okio的简单使用,无非下面几个步骤:
a.构建缓冲池、缓冲源对象
b.读、写
c.关闭缓冲对象;
构建缓冲对象
可以看到,直接我我们接触的就是这个类:Okio。Okio,通过:
public static BufferedSource buffer(Source source) {
throw new IllegalArgumentException("source == null");
return new RealBufferedSource(source);
}
public static BufferedSink buffer(Sink sink) {
if (sink == null)
throw new IllegalArgumentException("sink == null");
return new RealBufferedSink(sink);
}
这两个static方法,返回 读、写池:BufferedSource 、BufferedSink ;Source、Sink ,这两种参数从哪里来?*看看Okio下面两个static方法*:
public static Source source(File file) throws FileNotFoundException {
throw new IllegalArgumentException("file == null");
return source(new FileInputStream(file));
}
/** Returns a sink that writes to {@code file}. */
public static Sink sink(File file) throws FileNotFoundException {
if (file == null)
throw new IllegalArgumentException("file == null");
return sink(new FileOutputStream(file));
}
读、写操作
可以从代码中看到,其实真正的完成读写的类是:RealBufferedSource、RealBufferedSink
切到RealBufferedSink中
final class RealBufferedSink implements BufferedSink {...}
final class RealBufferedSource implements BufferedSource {...}
下面以写操作为例,来查看源码:
@Override public BufferedSink writeUtf8(String string, int beginIndex, int endIndex)
throws IOException {
if (closed) throw new IllegalStateException("closed");
buffer.writeUtf8(string, beginIndex, endIndex);
return emitCompleteSegments();
}
可以看到这里,有一个成员变量buffer,
public final Buffer buffer = new Buffer();
和emitCompleteSegments()完成写的提交;
@Override public BufferedSink emitCompleteSegments() throws IOException {
if (closed) throw new IllegalStateException("closed");
long byteCount = buffer.completeSegmentByteCount();//buffer来提交
/***sink这一行代码才是真正的io操作。
之前所有的writeXXX()方法,是将,数据保存在链表中,
在这一步中,将数据从链表中不出来;
注意,这里的sink对象,我们看看他是从哪里来的***/
return this;
}
在Okio.中buffer(XXX,X)来得到缓冲源、池对象,这里传入了一个Sink对象,这里的Sink就是我们emitCompleteSegments()中真正来完成IO操作的类:
public static BufferedSink buffer(Sink sink) {
if (sink == null)
throw new IllegalArgumentException("sink == null");
return new RealBufferedSink(sink);
}
通过前面的源码分析,我们知道,真正的缓冲池是:RealBufferedSink。
RealBufferedSink(Sink sink) {
throw new IllegalArgumentException("sink == null");
this.sink = sink;
}
这个Sink ,是Okio中sink()方法返回的:
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() {
/*看到没有,NEW 了一个Sink对象,并且实现了下面的几个方法,这就是RealBufferedSink 中emitCompleteSegments() 里,正真的Io操作的实现,过程有些复杂。下面我们贴一张流程图,来展示这个写的过程。*/
@Override public void write(Buffer source, long byteCount) throws IOException {
checkOffsetAndCount(source.size, 0, byteCount);
while (byteCount > 0) {
timeout.throwIfReached();
Segment head = source.head;
int toCopy = (int) Math.min(byteCount, head.limit - head.pos);
out.write(head.data, head.pos, toCopy);
head.pos += toCopy;
byteCount -= toCopy;
source.size -= toCopy;
if (head.pos == head.limit) {
source.head = head.pop();
SegmentPool.recycle(head);
}
}
}
@Override public void flush() throws IOException {
out.flush();
}
@Override public void close() throws IOException {
out.close();
}
@Override public Timeout timeout() {
return timeout;
}
@Override public String toString() {
return "sink(" + out + ")";
}
};
}
Okio写操作流程图
OkIO高效在哪里?
在前面我们说,okio对内存和计算量最了优化。所有的写数据操作,其实是:先保存在Segment这里双向链表的用其中,由Buffer对象来直接操作它,最后真正写的时候,由Sink的实现来完成。下面就来分析,Buffer操作链表的这部分代码
public final class Buffer implements BufferedSource, BufferedSink, Cloneable {
.....}
看看,buffer的:writeUtf8(string, beginIndex, endIndex);
@Override public Buffer writeUtf8(String string, int beginIndex, int endIndex) {
if (string == null) throw new IllegalArgumentException("string == null");
if (beginIndex < 0) throw new IllegalAccessError("beginIndex < 0: " + beginIndex);
if (endIndex < beginIndex) {
throw new IllegalArgumentException("endIndex < beginIndex: " + endIndex + " < " + beginIndex);
}
if (endIndex > string.length()) {
throw new IllegalArgumentException(
"endIndex > string.length: " + endIndex + " > " + string.length());
}
// 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 tail = writableSegment(1);
byte[] data = tail.data;
int segmentOffset = tail.limit - i;
int runLimit = Math.min(endIndex, Segment.SIZE - segmentOffset);
// Emit a 7-bit character with 1 byte.
data[segmentOffset + i++] = (byte) c; // 0xxxxxxx
// Fast-path contiguous runs of ASCII characters. This is ugly, but yields a ~4x performance
// improvement over independent calls to writeByte().
while (i < runLimit) {
c = string.charAt(i);
if (c >= 0x80) break;
data[segmentOffset + i++] = (byte) c; // 0xxxxxxx
}
int runSize = i + segmentOffset - tail.limit; // Equivalent to i - (previous i).
tail.limit += runSize;
size += runSize;
} else if (c < 0x800) {
// Emit a 11-bit character with 2 bytes.
writeByte(c >> 6 | 0xc0); // 110xxxxx
writeByte(c & 0x3f | 0x80); // 10xxxxxx
i++;
} else if (c < 0xd800 || c > 0xdfff) {
// Emit a 16-bit character with 3 bytes.
writeByte(c >> 12 | 0xe0); // 1110xxxx
writeByte(c >> 6 & 0x3f | 0x80); // 10xxxxxx
writeByte(c & 0x3f | 0x80); // 10xxxxxx
i++;
} else {
// c is a surrogate. Make sure it is a high surrogate & that its successor is a low
// surrogate. If not, the UTF-16 is invalid, in which case we emit a replacement character.
int low = i + 1 < endIndex ? string.charAt(i + 1) : 0;
if (c > 0xdbff || low < 0xdc00 || low > 0xdfff) {
writeByte('?');
i++;
continue;
}
// UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits)
// UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits)
// Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits)
int codePoint = 0x010000 + ((c & ~0xd800) << 10 | low & ~0xdc00);
// Emit a 21-bit character with 4 bytes.
writeByte(codePoint >> 18 | 0xf0); // 11110xxx
writeByte(codePoint >> 12 & 0x3f | 0x80); // 10xxxxxx
writeByte(codePoint >> 6 & 0x3f | 0x80); // 10xxyyyy
writeByte(codePoint & 0x3f | 0x80); // 10yyyyyy
i += 2;
}
}
return this;
}
/**
1.文本是被保存到一个Segment 对象里的,Segment 是一个双向链表的数据结构;
2.根据字符的取值范围,划定一定长度的空间来保存字符
3.writeByte是这个写字符的具体实现;
注:双向链表的,插入和读取速度都是不错的,
比数组快。后续数据可追加,而不是使用数组的复制,
数组的复制,无形中增加了CUP的负担。根据字符的取值范围,
来划定空间保存临时数据,提高了内存的使用效率。
*/
切到这个writeByte()方法中来:
@Override public Buffer writeByte(int b) {
Segment tail = writableSegment(1);//生成一个数据节点
tail.data[tail.limit++] = (byte) b;//将节点,添加到链表尾部,链表长度++
size += 1;
return this;
}
总结
1.整体架构与设计模式方面:
Okio,使用起来很简单,暴露给用户的就:Okio,BufferSink,BufferSource,Buffer这几个类,根据相面的源码分析,我们大致清楚了,Okio的类图关系如下;
;
可以看到,缓冲池、缓冲源、buffer、Segment都是缓存数据为服务,真正的Io,操作还是入上面流程图所描述由Sink、Source,new的那个对象实现;
要学习的是:
1..1源码作者,提供一个工具了类,OKio来处理,缓存和真正的读写与上图,处理缓存的类,完美解耦;
1.2从上图中看到,BufferSink、bufferSource 、buffer都继承了Sink和Source接口
是的缓存的具体业务,对应性的得到分解,调用时泾渭分明。
3.数据结构方面:
从这个项目中,可以看到,双向链表缓存数据,根据字符取值,给个节点分配不同长度空间来保存数据,节省了空间。双向链表的存取,俱佳,是支持OKio表现高效的核心;
本文介绍了Okio库在Android应用中的应用,详细解析了其简单易用的文件读写方法及内存优化策略。通过静态方法创建缓冲池和缓冲源,实现代理操作并优化内存和CPU使用,展示了Okio库如何提升应用程序性能。

1029

被折叠的 条评论
为什么被折叠?



