Okio源碼分析
Okio是Square公司推出的Java IO庫,也是OKHttp依賴的IO庫。今天花了兩個小時詳細研究了下。分享給大家。
老規矩,先放圖。
類圖
Okio.png
AnonymousSource類代表數據源,內部引用了InputStream。
Buffer類保存緩存數據,有個head的成員變量,指向的是以Segment為節點的鏈表的頭結點,Segment保存字節數組,是鏈表的節點類。
使用
假設我有一個test.txt文件,內容是 hello world ,現在我用Okio把它讀出來。
public static void main(String[] args) {
File file = new File("test.txt");
try {
readString(new FileInputStream(file));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void readString(InputStream in) throws IOException {
BufferedSource source = Okio.buffer(Okio.source(in)); //創建BufferedSource
String s = source.readUtf8(); //以UTF-8讀
System.out.println(s); //打印
pngSource.close();
}
--------------------------------------輸出-----------------------------------------
hello world
Okio是對Java底層io的封裝,所以底層io能做的Okio都能做。
創建BufferedSource對象
首先調用的是Okio.source(in),我們看下Okio.source方法
public static Source source(final InputStream in) {
return source(in, new Timeout());
}
private static Source source(final InputStream in, final Timeout timeout) {
if (in == null) throw new IllegalArgumentException("in == null");
if (timeout == null) throw new IllegalArgumentException("timeout == null");
return new Source() { //創建一個匿名Source對象,在類圖中我把它叫做AnonymousSource
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (byteCount == 0) return 0;
timeout.throwIfReached();
Segment tail = sink.writableSegment(1);
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
if (bytesRead == -1) return -1;
tail.limit += bytesRead;
sink.size += bytesRead;
return bytesRead;
}
@Override public void close() throws IOException {
in.close();
}
@Override public Timeout timeout() {
return timeout;
}
@Override public String toString() {
return "source(" + in + ")";
}
};
}
很簡單,創建并返回一個匿名Source對象,在類圖中我把這個匿名類叫做AnonymousSource。
接著調用 Okio.buffer() ,看下源碼:
public static BufferedSource buffer(Source source) {
if (source == null) throw new IllegalArgumentException("source == null");
return new RealBufferedSource(source);
}
創建一個RealBufferedSource對象,我們看下這個類
final class RealBufferedSource implements BufferedSource
核心成員變量
public final Buffer buffer;
public final Source source;
public RealBufferedSource(Source source) {
this(source, new Buffer()); //創建一個Buffer對象
}
RealBufferedSource繼承自BufferedSource,它有兩個核心成員變量 Buffer buffe和Source source ,分別包含緩存和數據源,看下類圖就明白了。到這里,再看下類圖,AnonymousSource、RealBufferedSource,Buffer都已經創建好了。
我們仔細看下Buffer類:
public final class Buffer implements BufferedSource, BufferedSink, Cloneable
成員變量
Segment head; //指向鏈表頭部
long size; //字節數
調用source.readUtf8()
這個source就是上面創建的RealBufferedSource對象,看下readUtf8方法:
@Override public String readUtf8() throws IOException {
buffer.writeAll(source); //將數據從source讀取到緩存buffer
return buffer.readUtf8(); //從緩存讀取數據
}
看下buffer.writeAll方法
@Override public long writeAll(Source source) throws IOException {
if (source == null) throw new IllegalArgumentException("source == null");
long totalBytesRead = 0;
for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) { //從source讀取數據
totalBytesRead += readCount;
}
return totalBytesRead;
}
該方法從source讀取數據,這個source是在Okio中創建的你們Souce對象。
return new Source() {
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (byteCount == 0) return 0;
timeout.throwIfReached();
Segment tail = sink.writableSegment(1); //從buffer獲取尾節點
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
int bytesRead = in.read(tail.data, tail.limit, maxToCopy); //從InputStream讀取數據到Buffer中的鏈表的尾節點
if (bytesRead == -1) return -1;
tail.limit += bytesRead;
sink.size += bytesRead;
return bytesRead;
}
@Override public void close() throws IOException {
in.close();
}
@Override public Timeout timeout() {
return timeout;
}
@Override public String toString() {
return "source(" + in + ")";
}
};
好了,到這里InputStream的數據就被保存到了Buffer中的鏈表中了。
接著應該調用RealBufferedSource.readUtf8()中的buffer.readUtf8()方法,看下源碼:
@Override public String readUtf8() {
try {
return readString(size, Util.UTF_8);
} catch (EOFException e) {
throw new AssertionError(e);
}
}
@Override public String readString(Charset charset) {
try {
return readString(size, charset);
} catch (EOFException e) {
throw new AssertionError(e);
}
}
@Override public String readString(long byteCount, Charset charset) throws EOFException {
checkOffsetAndCount(size, 0, byteCount);
if (charset == null) throw new IllegalArgumentException("charset == null");
if (byteCount > Integer.MAX_VALUE) {
throw new IllegalArgumentException("byteCount > Integer.MAX_VALUE: " + byteCount);
}
if (byteCount == 0) return "";
Segment s = head; //鏈表的頭結點
if (s.pos + byteCount > s.limit) { //如果頭結點的字節數,不夠我們讀,接著讀鏈表的下一個節點
// If the string spans multiple segments, delegate to readBytes().
return new String(readByteArray(byteCount), charset);
}
String result = new String(s.data, s.pos, (int) byteCount, charset);//頭節點的字節夠我們讀,直接用當前Segment的字節數據構造String對象
s.pos += byteCount;
size -= byteCount;
if (s.pos == s.limit) {
head = s.pop();
SegmentPool.recycle(s);
}
return result;
}
最終會從Buffer的鏈表中的頭節點開始讀取字節,如果頭結點的字節數,不夠我們讀,接著讀鏈表的下一個節點。如果頭節點的字節夠我們讀,直接用當前Segment的字節數據構造String對象。好了,一次讀取String的過程結束了。再去看看類圖,肯定清楚了。
總結
調有Okio的source方法會返回一個實現了Source接口的對象,這個對象引用了InputStream,所以它就代表了數據源。
Buffer保存從source讀取的字節,真正存儲字節在Segment對象中,Buffer保存著以Segment為節點的鏈表的頭結點,所以Buffer可以獲取所有數據。
來自:http://www.jianshu.com/p/ccf24a63dca8