Okio筆記
一、基本認(rèn)識(shí)
Okio庫(kù)是一個(gè)由square公司開(kāi)發(fā)的,它補(bǔ)充了java.io和java.nio的不足,以便能夠更加方便,快速地訪問(wèn)、存儲(chǔ)和處理數(shù)據(jù)。而OkHttp的底層也使用該庫(kù)作為支持。而在開(kāi)發(fā)中,使用該庫(kù)可以大大的帶來(lái)方便。
Okio中有兩個(gè)關(guān)鍵的接口,Sink和Source,這兩個(gè)接口都繼承了Closeable接口;而Sink可以簡(jiǎn)單的看做OutputStream,Source可以簡(jiǎn)單的看做InputStream,這兩個(gè)接口都是支持讀寫(xiě)超時(shí)設(shè)置的。

它們各自有一個(gè)支持緩沖區(qū)的子類(lèi)接口,BufferedSink和BufferedSource,而B(niǎo)ufferedSink有一個(gè)實(shí)現(xiàn)類(lèi)RealBufferedSink,BufferedSource有一個(gè)實(shí)現(xiàn)類(lèi)RealBufferedSource;此外,Sink和Source它門(mén)還各自有一個(gè)支持gzip壓縮的實(shí)現(xiàn)類(lèi)GzipSink和GzipSource;一個(gè)具有委托功能的抽象類(lèi)ForwardingSink和ForwardingSource;還有一個(gè)實(shí)現(xiàn)類(lèi)便是InflaterSource和DeflaterSink,這兩個(gè)類(lèi)主要用于壓縮,為GzipSink和GzipSource服務(wù);
BufferedSink中定義了一系列寫(xiě)入緩存區(qū)的方法,比如write方法寫(xiě)byte數(shù)組,writeUtf8寫(xiě)字符串,還有一些列的writeByte,writeString,writeShort,writeInt,writeLong,writeDecimalLong等等方法。BufferedSource定義的方法和BufferedSink極為相似,只不過(guò)一個(gè)是寫(xiě)一個(gè)是讀,基本上都是一一對(duì)應(yīng)的,如readUtf8,readByte,readString,readShort,readInt等等等等。這兩個(gè)接口中的方法有興趣的點(diǎn)源碼進(jìn)去看就可以了。
二、簡(jiǎn)單使用
//簡(jiǎn)單的文件讀寫(xiě)
public void readWriteFile() throws Exception {
//1.文件
File file = new File("resources/dest.txt");
//2.構(gòu)建寫(xiě)緩沖池
BufferedSink sink = Okio.buffer(Okio.sink(file));
//3.向緩沖池寫(xiě)入文本
sink.writeUtf8("Hello, java.io file!");
//4.關(guān)閉緩沖池
sink.close();
//1.構(gòu)建讀文件緩沖源
BufferedSource source = Okio.buffer(Okio.source(file));
//2.讀文件
source.readUtf8();
//3.關(guān)閉緩沖源
source.close();
}
//文件內(nèi)容的追加
public void appendFile() throws Exception {
File file = new File("resources/dest.txt");
//1.將文件讀入,并構(gòu)建寫(xiě)緩沖池
BufferedSink sink = Okio.buffer(Okio.appendingSink(file));
//2.追加文本
sink.writeUtf8("Hello, ");
//3.關(guān)閉
sink.close();
//4.再次追加文本,需要重新構(gòu)建緩沖池對(duì)象
sink = Okio.buffer(Okio.appendingSink(file));
//5.追加文本
sink.writeUtf8("java.io file!");
//6.關(guān)閉緩沖池
sink.close();
}
//通過(guò)路徑來(lái)讀寫(xiě)文件
public void readWritePath() throws Exception {
Path path = new File("resources/dest.txt").toPath();
//1.構(gòu)建寫(xiě)緩沖池
BufferedSink sink = Okio.buffer(Okio.sink(path));
//2.寫(xiě)緩沖
sink.writeUtf8("Hello, java.nio file!");
//3.關(guān)閉緩沖
sink.close();
//1.構(gòu)建讀緩沖源
BufferedSource source = Okio.buffer(Okio.source(path));
//2.讀文本
source.readUtf8();
//3.關(guān)閉緩沖源
source.close();
}
//寫(xiě)B(tài)uffer,在okio中Buffer是一個(gè)很重要的對(duì)象,在后面我們?cè)谠敿?xì)介紹。
public void sinkFromOutputStream() throws Exception {
//1.構(gòu)建buffer對(duì)象
Buffer data = new Buffer();
//2.向緩沖中寫(xiě)入文本
data.writeUtf8("a");
//3.可以連續(xù)追加,類(lèi)似StringBuffer
data.writeUtf8("c");
//4.構(gòu)建字節(jié)數(shù)組流對(duì)象
ByteArrayOutputStream out = new ByteArrayOutputStream();
//5.構(gòu)建寫(xiě)緩沖池
Sink sink = Okio.sink(out);
//6.向池中寫(xiě)入buffer
sink.write(data, 2);
}
//讀Buffer
public void sourceFromInputStream() throws Exception {
//1.構(gòu)建字節(jié)數(shù)組流
InputStream in = new ByteArrayInputStream(
("a" + "c").getBytes(UTF_8));
// Source: ac
//2.緩沖源
Source source = Okio.source(in);
//3.buffer
Buffer sink = new Buffer();
//4.將數(shù)據(jù)讀入buffer
sink.readUtf8(2);
}
//Gzip功能
public static void gzipTest(String[] args) {
Sink sink = null;
BufferedSink bufferedSink = null;
GzipSink gzipSink = null;
try {
File dest = new File("resources/gzip.txt");
sink = Okio.sink(dest);
gzipSink = new GzipSink(sink);
bufferedSink = Okio.buffer(gzipSink);
bufferedSink.writeUtf8("android vs ios");
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(bufferedSink);
}
Source source = null;
BufferedSource bufferedSource = null;
GzipSource gzipSource = null;
try {
File file = new File("resources/gzip.txt");
source = Okio.source(file);
gzipSource = new GzipSource(source);
bufferedSource = Okio.buffer(gzipSource);
String content = bufferedSource.readUtf8();
System.out.println(content);
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(bufferedSource);
}
}
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
可以看到,okio的文件讀寫(xiě)操作,使用起來(lái)是很簡(jiǎn)單的,減少了很多io操作的基本代碼,并且對(duì)內(nèi)存和cpu使用做了優(yōu)化(代碼直觀當(dāng)然是看不出來(lái)的,okio作者在buffer類(lèi)中有詳細(xì)的說(shuō)明,后面我們?cè)俜治鲞@個(gè)類(lèi))。
此外還有一個(gè)ByteString類(lèi),這個(gè)類(lèi)可以用來(lái)做各種變化,它將byte轉(zhuǎn)會(huì)為String,而這個(gè)String可以是utf8的值,也可以是base64后的值,也可以是md5的值,也可以是sha256的值,總之就是各種變化,最后取得你想要的值。
三、Okio框架結(jié)構(gòu)與源碼分析
Okio框架分析
Okio的簡(jiǎn)單使用,無(wú)非下面幾個(gè)步驟:
a. 構(gòu)建對(duì)象
b. 讀、寫(xiě)
c. 關(guān)閉緩沖對(duì)象
構(gòu)建緩沖對(duì)象
通過(guò)Okio的buffer(Source source)和buffer(Sink sink)方法,構(gòu)建緩沖對(duì)象
public static BufferedSource buffer(Source source) {
return new RealBufferedSource(source);
}
public static BufferedSink buffer(Sink sink) {
return new RealBufferedSink(sink);
}
這兩個(gè)static方法,返回 讀、寫(xiě)池:BufferedSource 、BufferedSink。Source、Sink ,這兩種參數(shù)從哪里來(lái)?看看Okio下面兩個(gè)static方法:
public static Sink sink(File file) throws FileNotFoundException {
if (file == null) throw new IllegalArgumentException("file == null");
return sink(new FileOutputStream(file));
}
public static Source source(File file) throws FileNotFoundException {
if (file == null) throw new IllegalArgumentException("file == null");
return source(new FileInputStream(file));
}
類(lèi)似的方法還有
sink(File file) Sinksink(OutputStream out) Sinksink(Path path, OpenOption... options) Sinksink(Socket socket) Sinksource(File file) Sourcesource(InputStream in) Sourcesource(Path path, OpenOption... options) Sourcesource(Socket socket) Source
以sink方法為例,最后都是調(diào)用
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了一個(gè)Sink對(duì)象,這個(gè)對(duì)象就是emitCompleteSegments方法中的sink
@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);//真正完成寫(xiě)操作!
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 + ")";
}
};
}
讀、寫(xiě)操作
下面以寫(xiě)操作為例,來(lái)查看源碼,在RealBufferedSink中
@Override
public BufferedSink writeUtf8(String string, int beginIndex, int endIndex)
throws IOException {
if (closed) throw new IllegalStateException("closed");
buffer.writeUtf8(string, beginIndex, endIndex);//寫(xiě)入到buffer中
return emitCompleteSegments();//將buffer中的內(nèi)容寫(xiě)入到sink成員變量中去,然后將自身返回
}
可以看到這里,有一個(gè)成員變量buffer,并調(diào)用了其writeUtf8方法,源碼如下:
@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 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) {
...
}
}
return this;
}
@Override
public Buffer writeByte(int b) {
//返回一個(gè)可寫(xiě)的Segment,可以使用的capacity至少為1(一個(gè)Segment的總大小為8KB),若當(dāng)前Segment已經(jīng)寫(xiě)滿(mǎn)了,則會(huì)新建一個(gè)Segment返回
Segment tail = writableSegment(1);
tail.data[tail.limit++] = (byte) b;//將入?yún)⒎诺絊egment中
size += 1;//總長(zhǎng)度+1
return this;
}
這一切的背后都是一個(gè)叫做Buffer的類(lèi)在支持著緩沖區(qū),Buffer是BufferedSink和BufferedSource的實(shí)現(xiàn)類(lèi),因此它既可以用來(lái)讀數(shù)據(jù),也可以用來(lái)寫(xiě)數(shù)據(jù),其內(nèi)部使用了一個(gè)Segment和SegmentPool,維持著一個(gè)鏈表,其循環(huán)利用的機(jī)制和Android中Message的利用機(jī)制是一模一樣的。
final class SegmentPool {//這是一個(gè)鏈表,Segment是其元素,總大小為8KB
static final long MAX_SIZE = 64 * 1024; // 64 KiB.
static Segment next;//指向鏈表的下一個(gè)元素
static long byteCount;
private SegmentPool() {
}
static Segment take() {
synchronized (SegmentPool.class) {
if (next != null) {//從鏈表中取出一個(gè)Segment
Segment result = next;
next = result.next;
result.next = null;
byteCount -= Segment.SIZE;
return result;
}
}
return new Segment(); // Pool is empty. Don't zero-fill while holding a lock.
}
static void recycle(Segment segment) {
if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
if (segment.shared) return; // This segment cannot be recycled.
synchronized (SegmentPool.class) {
if (byteCount + Segment.SIZE > MAX_SIZE) return; // Pool is full.
byteCount += Segment.SIZE;//Segment.SIZE固定為8KB
segment.next = next;
segment.pos = segment.limit = 0;
next = segment;
}
}
}
內(nèi)部一個(gè)成員變量next指向鏈表下一個(gè)元素,take方法首先判斷池中是否存在可用的,存在則返回,不存在則new一個(gè),而recycle則是將不再使用的Segment重新扔到池中去。從而達(dá)到一個(gè)Segment池的作用。
回到writeUtf8方法中,RealBufferedSink的emitCompleteSegments()方法完成寫(xiě)的提交
@Override
public BufferedSink emitCompleteSegments() throws IOException {
if (closed) throw new IllegalStateException("closed");
long byteCount = buffer.completeSegmentByteCount();//返回已經(jīng)緩存的字節(jié)數(shù)
if (byteCount > 0) sink.write(buffer, byteCount);//這個(gè)sink就是剛才new的
return this;
}