Okio筆記
一恨旱、基本認(rèn)識
Okio庫是一個由square公司開發(fā)的,它補充了java.io和java.nio的不足坝疼,以便能夠更加方便搜贤,快速地訪問、存儲和處理數(shù)據(jù)钝凶。而OkHttp的底層也使用該庫作為支持仪芒。而在開發(fā)中,使用該庫可以大大的帶來方便耕陷。
Okio中有兩個關(guān)鍵的接口掂名,Sink和Source,這兩個接口都繼承了Closeable接口哟沫;而Sink可以簡單的看做OutputStream饺蔑,Source可以簡單的看做InputStream,這兩個接口都是支持讀寫超時設(shè)置的嗜诀。
它們各自有一個支持緩沖區(qū)的子類接口猾警,BufferedSink和BufferedSource,而BufferedSink有一個實現(xiàn)類RealBufferedSink裹虫,BufferedSource有一個實現(xiàn)類RealBufferedSource肿嘲;此外,Sink和Source它門還各自有一個支持gzip壓縮的實現(xiàn)類GzipSink和GzipSource筑公;一個具有委托功能的抽象類ForwardingSink和ForwardingSource雳窟;還有一個實現(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等等等等。這兩個接口中的方法有興趣的點源碼進去看就可以了兰绣。
二世分、簡單使用
//簡單的文件讀寫
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是一個很重要的對象,在后面我們在詳細介紹雌澄。
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類中有詳細的說明,后面我們再分析這個類)旗唁。
此外還有一個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的實現(xiàn)類,因此它既可以用來讀數(shù)據(jù)尚卫,也可以用來寫數(shù)據(jù)归榕,其內(nèi)部使用了一個Segment和SegmentPool,維持著一個鏈表吱涉,其循環(huán)利用的機制和Android中Message的利用機制是一模一樣的刹泄。
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重新扔到池中去。從而達到一個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;
}