Okio筆記
一、基本認識
Okio庫是一個由square公司開發的,它補充了java.io和java.nio的不足,以便能夠更加方便,快速地訪問、存儲和處理數據。而OkHttp的底層也使用該庫作為支持。而在開發中,使用該庫可以大大的帶來方便。
Okio中有兩個關鍵的接口,Sink和Source,這兩個接口都繼承了Closeable接口;而Sink可以簡單的看做OutputStream,Source可以簡單的看做InputStream,這兩個接口都是支持讀寫超時設置的。
它們各自有一個支持緩沖區的子類接口,BufferedSink和BufferedSource,而BufferedSink有一個實現類RealBufferedSink,BufferedSource有一個實現類RealBufferedSource;此外,Sink和Source它門還各自有一個支持gzip壓縮的實現類GzipSink和GzipSource;一個具有委托功能的抽象類ForwardingSink和ForwardingSource;還有一個實現類便是InflaterSource和DeflaterSink,這兩個類主要用于壓縮,為GzipSink和GzipSource服務;
BufferedSink中定義了一系列寫入緩存區的方法,比如write方法寫byte數組,writeUtf8寫字符串,還有一些列的writeByte,writeString,writeShort,writeInt,writeLong,writeDecimalLong等等方法。BufferedSource定義的方法和BufferedSink極為相似,只不過一個是寫一個是讀,基本上都是一一對應的,如readUtf8,readByte,readString,readShort,readInt等等等等。這兩個接口中的方法有興趣的點源碼進去看就可以了。
二、簡單使用
//簡單的文件讀寫
public void readWriteFile() throws Exception {
//1.文件
File file = new File("resources/dest.txt");
//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();
}
//文件內容的追加
public void appendFile() throws Exception {
File file = new File("resources/dest.txt");
//1.將文件讀入,并構建寫緩沖池
BufferedSink sink = Okio.buffer(Okio.appendingSink(file));
//2.追加文本
sink.writeUtf8("Hello, ");
//3.關閉
sink.close();
//4.再次追加文本,需要重新構建緩沖池對象
sink = Okio.buffer(Okio.appendingSink(file));
//5.追加文本
sink.writeUtf8("java.io file!");
//6.關閉緩沖池
sink.close();
}
//通過路徑來讀寫文件
public void readWritePath() throws Exception {
Path path = new File("resources/dest.txt").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是一個很重要的對象,在后面我們在詳細介紹。
public void sinkFromOutputStream() throws Exception {
//1.構建buffer對象
Buffer data = new Buffer();
//2.向緩沖中寫入文本
data.writeUtf8("a");
//3.可以連續追加,類似StringBuffer
data.writeUtf8("c");
//4.構建字節數組流對象
ByteArrayOutputStream out = new ByteArrayOutputStream();
//5.構建寫緩沖池
Sink sink = Okio.sink(out);
//6.向池中寫入buffer
sink.write(data, 2);
}
//讀Buffer
public void sourceFromInputStream() throws Exception {
//1.構建字節數組流
InputStream in = new ByteArrayInputStream(
("a" + "c").getBytes(UTF_8));
// Source: ac
//2.緩沖源
Source source = Okio.source(in);
//3.buffer
Buffer sink = new Buffer();
//4.將數據讀入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操作的基本代碼,并且對內存和cpu使用做了優化(代碼直觀當然是看不出來的,okio作者在buffer類中有詳細的說明,后面我們再分析這個類)。
此外還有一個ByteString類,這個類可以用來做各種變化,它將byte轉會為String,而這個String可以是utf8的值,也可以是base64后的值,也可以是md5的值,也可以是sha256的值,總之就是各種變化,最后取得你想要的值。
三、Okio框架結構與源碼分析
Okio框架分析
Okio的簡單使用,無非下面幾個步驟:
a. 構建對象
b. 讀、寫
c. 關閉緩沖對象
構建緩沖對象
通過Okio的buffer(Source source)
和buffer(Sink sink)
方法,構建緩沖對象
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 ,這兩種參數從哪里來?看看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方法為例,最后都是調用
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中的內容寫入到sink成員變量中去,然后將自身返回
}
可以看到這里,有一個成員變量buffer,并調用了其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),若當前Segment已經寫滿了,則會新建一個Segment返回
Segment tail = writableSegment(1);
tail.data[tail.limit++] = (byte) b;//將入參放到Segment中
size += 1;//總長度+1
return this;
}
這一切的背后都是一個叫做Buffer的類在支持著緩沖區,Buffer是BufferedSink和BufferedSource的實現類,因此它既可以用來讀數據,也可以用來寫數據,其內部使用了一個Segment和SegmentPool,維持著一個鏈表,其循環利用的機制和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;
}
}
}
內部一個成員變量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();//返回已經緩存的字節數
if (byteCount > 0) sink.write(buffer, byteCount);//這個sink就是剛才new的
return this;
}