Okio筆記
一睦番、基本認(rèn)識
Okio庫是一個由square公司開發(fā)的喜爷,它補(bǔ)充了java.io和java.nio的不足当窗,以便能夠更加方便够坐,快速地訪問、存儲和處理數(shù)據(jù)。而OkHttp的底層也使用該庫作為支持元咙。而在開發(fā)中梯影,使用該庫可以大大的帶來方便。
Okio中有兩個關(guān)鍵的接口庶香,Sink和Source光酣,這兩個接口都繼承了Closeable接口;而Sink可以簡單的看做OutputStream脉课,Source可以簡單的看做InputStream救军,這兩個接口都是支持讀寫超時設(shè)置的。
它們各自有一個支持緩沖區(qū)的子類接口倘零,BufferedSink和BufferedSource唱遭,而BufferedSink有一個實(shí)現(xiàn)類RealBufferedSink,BufferedSource有一個實(shí)現(xiàn)類RealBufferedSource呈驶;此外拷泽,Sink和Source它門還各自有一個支持gzip壓縮的實(shí)現(xiàn)類GzipSink和GzipSource;一個具有委托功能的抽象類ForwardingSink和ForwardingSource袖瞻;還有一個實(shí)現(xiàn)類便是InflaterSource和DeflaterSink司致,這兩個類主要用于壓縮,為GzipSink和GzipSource服務(wù)聋迎;
BufferedSink中定義了一系列寫入緩存區(qū)的方法脂矫,比如write方法寫byte數(shù)組,writeUtf8寫字符串霉晕,還有一些列的writeByte庭再,writeString,writeShort牺堰,writeInt拄轻,writeLong,writeDecimalLong等等方法伟葫。BufferedSource定義的方法和BufferedSink極為相似恨搓,只不過一個是寫一個是讀,基本上都是一一對應(yīng)的筏养,如readUtf8斧抱,readByte,readString撼玄,readShort夺姑,readInt等等等等墩邀。這兩個接口中的方法有興趣的點(diǎn)源碼進(jìn)去看就可以了掌猛。
二、簡單使用
//簡單的文件讀寫
public void readWriteFile() throws Exception {
//1.文件
File file = new File("resources/dest.txt");
//2.構(gòu)建寫緩沖池
BufferedSink sink = Okio.buffer(Okio.sink(file));
//3.向緩沖池寫入文本
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)建寫緩沖池
BufferedSink sink = Okio.buffer(Okio.appendingSink(file));
//2.追加文本
sink.writeUtf8("Hello, ");
//3.關(guān)閉
sink.close();
//4.再次追加文本荔茬,需要重新構(gòu)建緩沖池對象
sink = Okio.buffer(Okio.appendingSink(file));
//5.追加文本
sink.writeUtf8("java.io file!");
//6.關(guān)閉緩沖池
sink.close();
}
//通過路徑來讀寫文件
public void readWritePath() throws Exception {
Path path = new File("resources/dest.txt").toPath();
//1.構(gòu)建寫緩沖池
BufferedSink sink = Okio.buffer(Okio.sink(path));
//2.寫緩沖
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();
}
//寫B(tài)uffer废膘,在okio中Buffer是一個很重要的對象,在后面我們在詳細(xì)介紹慕蔚。
public void sinkFromOutputStream() throws Exception {
//1.構(gòu)建buffer對象
Buffer data = new Buffer();
//2.向緩沖中寫入文本
data.writeUtf8("a");
//3.可以連續(xù)追加丐黄,類似StringBuffer
data.writeUtf8("c");
//4.構(gòu)建字節(jié)數(shù)組流對象
ByteArrayOutputStream out = new ByteArrayOutputStream();
//5.構(gòu)建寫緩沖池
Sink sink = Okio.sink(out);
//6.向池中寫入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的文件讀寫操作孔飒,使用起來是很簡單的灌闺,減少了很多io操作的基本代碼,并且對內(nèi)存和cpu使用做了優(yōu)化(代碼直觀當(dāng)然是看不出來的坏瞄,okio作者在buffer類中有詳細(xì)的說明桂对,后面我們再分析這個類)。
此外還有一個ByteString類鸠匀,這個類可以用來做各種變化蕉斜,它將byte轉(zhuǎn)會為String,而這個String可以是utf8的值缀棍,也可以是base64后的值宅此,也可以是md5的值,也可以是sha256的值爬范,總之就是各種變化父腕,最后取得你想要的值。
三青瀑、Okio框架結(jié)構(gòu)與源碼分析
Okio框架分析
Okio的簡單使用侣诵,無非下面幾個步驟:
a. 構(gòu)建對象
b. 讀、寫
c. 關(guān)閉緩沖對象
構(gòu)建緩沖對象
通過Okio的buffer(Source source)
和buffer(Sink sink)
方法狱窘,構(gòu)建緩沖對象
public static BufferedSource buffer(Source source) {
return new RealBufferedSource(source);
}
public static BufferedSink buffer(Sink sink) {
return new RealBufferedSink(sink);
}
這兩個static方法杜顺,返回 讀、寫池:BufferedSource 蘸炸、BufferedSink躬络。Source、Sink 搭儒,這兩種參數(shù)從哪里來?看看Okio下面兩個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));
}
類似的方法還有
sink(File file) Sink
sink(OutputStream out) Sink
sink(Path path, OpenOption... options) Sink
sink(Socket socket) Sink
source(File file) Source
source(InputStream in) Source
source(Path path, OpenOption... options) Source
source(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了一個Sink對象,這個對象就是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);//真正完成寫操作淹禾!
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 + ")";
}
};
}
讀馁菜、寫操作
下面以寫操作為例,來查看源碼铃岔,在RealBufferedSink中
@Override
public BufferedSink writeUtf8(String string, int beginIndex, int endIndex)
throws IOException {
if (closed) throw new IllegalStateException("closed");
buffer.writeUtf8(string, beginIndex, endIndex);//寫入到buffer中
return emitCompleteSegments();//將buffer中的內(nèi)容寫入到sink成員變量中去汪疮,然后將自身返回
}
可以看到這里,有一個成員變量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) {
//返回一個可寫的Segment卖丸,可以使用的capacity至少為1(一個Segment的總大小為8KB),若當(dāng)前Segment已經(jīng)寫滿了盏道,則會新建一個Segment返回
Segment tail = writableSegment(1);
tail.data[tail.limit++] = (byte) b;//將入?yún)⒎诺絊egment中
size += 1;//總長度+1
return this;
}
這一切的背后都是一個叫做Buffer的類在支持著緩沖區(qū),Buffer是BufferedSink和BufferedSource的實(shí)現(xiàn)類稍浆,因此它既可以用來讀數(shù)據(jù),也可以用來寫數(shù)據(jù)猜嘱,其內(nèi)部使用了一個Segment和SegmentPool衅枫,維持著一個鏈表,其循環(huán)利用的機(jī)制和Android中Message的利用機(jī)制是一模一樣的朗伶。
final class SegmentPool {//這是一個鏈表为鳄,Segment是其元素,總大小為8KB
static final long MAX_SIZE = 64 * 1024; // 64 KiB.
static Segment next;//指向鏈表的下一個元素
static long byteCount;
private SegmentPool() {
}
static Segment take() {
synchronized (SegmentPool.class) {
if (next != null) {//從鏈表中取出一個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)部一個成員變量next指向鏈表下一個元素腕让,take方法首先判斷池中是否存在可用的孤钦,存在則返回,不存在則new一個纯丸,而recycle則是將不再使用的Segment重新扔到池中去偏形。從而達(dá)到一個Segment池的作用。
回到writeUtf8
方法中觉鼻,RealBufferedSink的emitCompleteSegments()
方法完成寫的提交
@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);//這個sink就是剛才new的
return this;
}