JDK的io庫由于歷史原因設計的比較復雜,有很多裝飾類告嘲,使用起來需要記憶大量的類,相信你也對此早已詬病不滿奖地。Square公司推出的Okio應運而生橄唬,它原本是作為Okhttp的io功能庫而設計的,也是因為Okhttp而被大家熟知参歹。從知道到會使用仰楚,再到理解實現(xiàn)原理后熟練使用,甚至在此基礎上二次開發(fā)優(yōu)化犬庇,這個認知的過程需要刻意練習僧界,這篇文章就是對Okio的一個總結(jié),Okio雖然代碼量不是很多臭挽, 但是里面值得學習的地方還是很多捂襟。
Source + Sink
簡介
Okio定義了自己的一套繼承鏈,Source對應InputStream欢峰, Sink對應OutputStream葬荷,這樣對比就不難理解了,看一下接口的定義
public interface Source extends Closeable {
long read(Buffer sink, long byteCount) throws IOException;
Timeout timeout();
@Override void close() throws IOException;
}
public interface Sink extends Closeable, Flushable {
void write(Buffer source, long byteCount) throws IOException;
@Override void flush() throws IOException;
Timeout timeout();
@Override void close() throws IOException;
}
接口定義的方法很簡潔纽帖,
read/write方法宠漩,讀取和寫入數(shù)據(jù)的接口方法,它們的第一個參數(shù)都是Buffer,關于這個類后面會詳細介紹,這里我們暫且按照緩沖區(qū)理解它钱贯。byteCount就是讀取或者寫入的字節(jié)數(shù)厌处。
timeout方法,Okio新增的新特性凝危,超時控制
close方法波俄,關閉輸入輸出流
flush方法,將Buffer緩沖區(qū)中的數(shù)據(jù)寫入目標流中蛾默。
如何使用
Okio已經(jīng)幫我們定義了一個門面類懦铺,名字就叫Okio,通過它可以生成各種我們需要的對象支鸡。
比如Okio.source(inputStream); 將inputStream包裝成我們的Source對象冬念,同樣的
Okio.sink(outputStream)趁窃;將outputStream包裝成Sink對象。
所以Okio的底層操作的流對象還是Jdk里面定義的InputStream和OutputStream急前,作為一個輕量級的io框架它不可能跳出Jdk的框架去另外實現(xiàn)一套醒陆,它做的只是方便開發(fā)者的封裝,但是它的封裝設計足夠優(yōu)秀裆针,這也是我還在這里跟你們吹牛x的原因刨摩,還是不想從大神那里學個一招半式。
看個例子
File file = new File("/Users/aliouswang/Documents/olympic/JavaArt//text.temp");
Sink sink = Okio.sink(file);
Buffer buffer = new Buffer();
buffer.writeString("Hello okio!", Charset.forName("UTF-8"));
buffer.writeInt(998);
buffer.writeByte(1);
buffer.writeLong(System.currentTimeMillis());
buffer.writeUtf8("Hello end!");
sink.write(buffer, buffer.size());
sink.flush();
sink.close();
很簡單的一個寫文件的例子世吨,前面說過Source和Sink的讀和寫的方法都需要一個Buffer對象澡刹,Buffer對象幫我們提供了類似BufferedInputStream和BufferedOutputStream的緩沖區(qū)功能(提高讀寫效率),同時還提供了DataInputStream和DataOutputStream中的大部分功能(比如寫int耘婚,byte罢浇,long等),而且Buffer還提供了寫String的方法边篮,更是為我們經(jīng)常使用的UTF-8編碼格式己莺,單獨提供讀寫方法。
有寫就有讀
Source source = Okio.source(file);
buffer.clear();
source.read(buffer, 1024);
String string = buffer.readString("Hello okio!".length(), Charset.forName("UTF-8"));
int intValue = buffer.readInt();
byte byteValue = buffer.readByte();
long longValue = buffer.readLong();
String utf8 = buffer.readUtf8();
System.out.println("str:" + string + ";\nint:" + intValue + ";\nbyte:" + byteValue + ";" +
"\nlong:" + longValue + "\nutf8:" + utf8);
source.close();
// 打印結(jié)果:
str:Hello okio!;
int:998;
byte:1;
long:1555325659665
utf8:Hello end!
但是每次都去new一個Buffer對象戈轿,是不是很麻煩凌受,你我都能想到的,大神們肯定早就想到了思杯,于是乎有了BufferedSink胜蛉,BufferedSource。
BufferedSource + BufferedSink
BufferedSource 和 BufferedSink 也都是接口色乾,里面定義的接口方法比較多誊册,篇幅關系,這里只列出BufferedSink的定義暖璧,更細節(jié)的可以查看源碼案怯,源碼中對很多方法的注釋都舉了例子來幫助我們理解,Okio的作者也是用心良苦澎办,生怕我們廣大的碼農(nóng)們看不懂嘲碱,不會用啊>质础B缶狻!
public interface BufferedSink extends Sink, WritableByteChannel {
Buffer buffer();
BufferedSink write(ByteString byteString) throws IOException;
BufferedSink write(byte[] source) throws IOException;
BufferedSink write(byte[] source, int offset, int byteCount) throws IOException;
long writeAll(Source source) throws IOException;
BufferedSink write(Source source, long byteCount) throws IOException;
BufferedSink writeUtf8(String string) throws IOException;
BufferedSink writeString(String string, Charset charset) throws IOException;
BufferedSink writeString(String string, int beginIndex, int endIndex, Charset charset)
throws IOException;
BufferedSink writeByte(int b) throws IOException;
BufferedSink writeShort(int s) throws IOException;
BufferedSink writeShortLe(int s) throws IOException;
BufferedSink writeInt(int i) throws IOException;
BufferedSink writeIntLe(int i) throws IOException;
BufferedSink writeLong(long v) throws IOException;
BufferedSink writeLongLe(long v) throws IOException;
BufferedSink writeDecimalLong(long v) throws IOException;
BufferedSink writeHexadecimalUnsignedLong(long v) throws IOException;
@Override void flush() throws IOException;
BufferedSink emit() throws IOException;
BufferedSink emitCompleteSegments() throws IOException;
OutputStream outputStream();
}
可以看到BufferedSink繼承于Sink琅绅,同時還繼承了WritableByteChannel扶欣,這個接口是nio接口,所以Okio同樣實現(xiàn)了nio的相關功能,這里由于水平有限料祠,關于nio的知識這篇文章不會涉及骆捧,有興趣的同學可以自行查閱資料哦。
BufferedSink定義了Buffer類中定義的全部方法髓绽,同時還定義了一個buffer()方法凑懂,返回一個Buffer對象,我們大概可以猜想到梧宫,這里應該是一個不太標準的代理模式接谨,BufferedSink委托Buffer來干活。
Okio同樣提供了Buffer相關的方法方便我們使用塘匣。
public static BufferedSink buffer(Sink sink) {
return new RealBufferedSink(sink);
}
public static BufferedSource buffer(Source source) {
return new RealBufferedSource(source);
}
返回的是BufferedSink 和 BufferedSource脓豪,Okio的默認實現(xiàn)類是RealBufferedSink和RealBufferedSource,我們可以通過BufferedSource和BufferedSink對上面讀寫文件的例子進行修改忌卤,
File file = new File("/Users/aliouswang/Documents/java/JavaArt/text.temp");
Sink sink = Okio.sink(file);
BufferedSink bufferedSink = Okio.buffer(sink);
bufferedSink.writeString("Hello okio!", Charset.forName("UTF-8"));
bufferedSink.writeInt(998);
bufferedSink.writeByte(1);
bufferedSink.writeLong(System.currentTimeMillis());
bufferedSink.writeUtf8("Hello end!");
bufferedSink.close();
Source source = Okio.source(file);
BufferedSource bufferedSource = Okio.buffer(source);
String string = bufferedSource.readString("Hello okio!".length(), Charset.forName("UTF-8"));
int intValue = bufferedSource.readInt();
byte byteValue = bufferedSource.readByte();
long longValue = bufferedSource.readLong();
String utf8 = bufferedSource.readUtf8();
System.out.println("str:" + string + ";\nint:" + intValue + ";\nbyte:" + byteValue + ";" +
"\nlong:" + longValue + "\nutf8:" + utf8);
source.close();
可以看到扫夜,BufferedSource和BufferedSink能夠滿足我們對io的日常絕大部分使用場景。
Okio門面類的實現(xiàn)
更一般的驰徊,我們會這樣去寫笤闯,鏈式調(diào)用,代碼更簡潔棍厂。
BufferedSource bufferedSource = Okio.buffer(Okio.source(file));
BufferedSink bufferedSink = Okio.buffer(Okio.sink(file));
非常簡潔的就能生成BufferedSource和BufferedSink颗味,看一下Okio幫我們做了什么。
public static Source source(File file) throws FileNotFoundException {
if (file == null) throw new IllegalArgumentException("file == null");
return source(new FileInputStream(file));
}
public static Source source(InputStream in) {
// 生成一個默認的Timeout超時對象牺弹,默認實現(xiàn)是沒有超時deadtime的
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() {
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (byteCount == 0) return 0;
try {
// 檢查超時浦马,
timeout.throwIfReached();
// 從Buffer獲取一個可以寫入的Segment,這一塊只是接下來再具體分析
Segment tail = sink.writableSegment(1);
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
// 將最大能copy的字節(jié)寫入Buffer张漂,
int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
if (bytesRead == -1) return -1;
tail.limit += bytesRead;
sink.size += bytesRead;
return bytesRead;
} catch (AssertionError e) {
if (isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
}
}
@Override public void close() throws IOException {
in.close();
}
@Override public Timeout timeout() {
return timeout;
}
@Override public String toString() {
return "source(" + in + ")";
}
};
}
通過Okio.source的實現(xiàn)可以看到晶默,在讀取的時候,會從傳入的InputStream in 對象中讀取字節(jié)到Buffer sink中航攒,前面我們提到過磺陡,RealBufferedSource和RealBufferedSink內(nèi)部都持有一個Buffer對象,可以猜測漠畜,它們持有的buffer對象 會在讀寫的時候傳入币他。我們進入源碼驗證一下, 這里我們以readString 方法為例。
@Override public String readString(long byteCount, Charset charset) throws IOException {
require(byteCount);
if (charset == null) throw new IllegalArgumentException("charset == null");
return buffer.readString(byteCount, charset);
}
@Override public void require(long byteCount) throws IOException {
if (!request(byteCount)) throw new EOFException();
}
@Override public boolean request(long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
while (buffer.size < byteCount) {
if (source.read(buffer, Segment.SIZE) == -1) return false;
}
return true;
}
@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);
s.pos += byteCount;
size -= byteCount;
if (s.pos == s.limit) {
head = s.pop();
SegmentPool.recycle(s);
}
return result;
}
代碼比較清晰盆驹,先從source中讀取要求的bytecount長度的String到buffer中圆丹,然后從buffer中讀取String 返回滩愁。其他的讀取方法跟readString大同小異躯喇,有興趣同學可以自行查閱源碼。
說了這么久,我們的主角Buffer對象登場了廉丽。
Buffer
看一下Buffer類的申明倦微,實現(xiàn)了BufferedSource, BufferedSink, Cloneable, ByteChannel 四個接口。
public final class Buffer implements BufferedSource, BufferedSink, Cloneable, ByteChannel {...}
我們知道Buffer作為緩沖區(qū)正压,肯定底層需要有數(shù)據(jù)結(jié)構來存儲暫存的數(shù)據(jù)欣福,JDK的BuffedInputStream和BufferedOutputStream中是使用字節(jié)數(shù)組的,而這里Okio的Buffer不是焦履,它使用的是Segment拓劝。
public Segment head;
Segment
Segment 是一個雙向循環(huán)鏈表,它的內(nèi)部持有一個byte[] data嘉裤,默認大小8192(與JDK的BufferedInputStream相同)郑临。
public final class Segment {
/** The size of all segments in bytes. */
static final int SIZE = 8192;
/** 默認共享最小字節(jié)數(shù)*/
static final int SHARE_MINIMUM = 1024;
final byte[] data;
/** 標識下一個讀取字節(jié)的位置 */
int pos;
/** 標識下一個寫入字節(jié)的位置 */
int limit;
/** 是否與其他Segment共享byte[] */
boolean shared;
/** 是否擁有這個byte[], 如果擁有可以寫入 */
boolean owner;
/** Segment后繼 */
public Segment next;
/** Segment前驅(qū) */
Segment prev;
Segment() {
this.data = new byte[SIZE];
this.owner = true;
this.shared = false;
}
......
}
Sement關鍵的成員變量都加了注釋,Okio為了優(yōu)化性能屑宠,避免頻繁的創(chuàng)建和回收對象厢洞,使用了對象池模式,設計了SegmentPool類來管理Segment典奉。
SegemntPool
final class SegmentPool {
/** The maximum number of bytes to pool. */
// TODO: Is 64 KiB a good maximum size? Do we ever have that many idle segments?
static final long MAX_SIZE = 64 * 1024; // 64 KiB.
/** Singly-linked list of segments. */
static @Nullable Segment next;
/** Total bytes in this pool. */
static long byteCount;
private SegmentPool() {
}
static Segment take() {
synchronized (SegmentPool.class) {
if (next != null) {
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.next = next;
segment.pos = segment.limit = 0;
next = segment;
}
}
}
SegmentPool代碼很簡潔躺翻,它的最大容量是8個Segment,如果超過調(diào)用take方法就會直接新建一個Segment對象卫玖,另外recycle回收方法負責回收閑置的Segment公你,將其加入鏈表,供其他buffer使用假瞬。
有了Segment和SegmentPool的知識省店,就更容易理解Buffer類的實現(xiàn)了。
比如Okio.source方法新建的Source對象的read方法笨触,獲取可以寫入的Segment對象懦傍,便利Segment鏈表獲取可以寫入的Segment,如果head為null則新建一個Segment芦劣。
// 從Buffer獲取一個可以寫入的Segment粗俱,這一塊只是接下來再具體分析
Segment tail = sink.writableSegment(1);
Segment writableSegment(int minimumCapacity) {
if (minimumCapacity < 1 || minimumCapacity > Segment.SIZE) throw new IllegalArgumentException();
if (head == null) {
head = SegmentPool.take(); // Acquire a first segment.
return head.next = head.prev = head;
}
Segment tail = head.prev;
if (tail.limit + minimumCapacity > Segment.SIZE || !tail.owner) {
tail = tail.push(SegmentPool.take()); // Append a new empty segment to fill up.
}
return tail;
}
Buffer的其他方法就不一一分析了,有了我們前面的知識虚吟,相信看起來不會太難寸认。接下來我們看一下Okio的另一個類ByteString。
ByteString
我們知道String是的內(nèi)部是基于char[] 數(shù)組來實現(xiàn)的串慰,Okio的ByteString內(nèi)部是基于byte[] 數(shù)組來實現(xiàn)的偏塞。跟String類似,ByteString也被設計為不可變的邦鲫,這樣可以保證ByteString是線程安全的灸叼。
public class ByteString implements Serializable, Comparable<ByteString> {
final byte[] data;
ByteString(byte[] data) {
this.data = data; // Trusted internal constructor doesn't clone data.
}
......
}
同時ByteString提供了很多方便的工具方法神汹,比如base64,sha1加密等古今。
public String base64() {
return Base64.encode(data);
}
/** Returns the 128-bit MD5 hash of this byte string. */
public ByteString md5() {
return digest("MD5");
}
/** Returns the 160-bit SHA-1 hash of this byte string. */
public ByteString sha1() {
return digest("SHA-1");
}
/** Returns the 256-bit SHA-256 hash of this byte string. */
public ByteString sha256() {
return digest("SHA-256");
}
/** Returns the 512-bit SHA-512 hash of this byte string. */
public ByteString sha512() {
return digest("SHA-512");
}
同時ByteString也提供了靜態(tài)方法屁魏,方便與String類型互轉(zhuǎn)。
/** Returns a new byte string containing the {@code UTF-8} bytes of {@code s}. */
public static ByteString encodeUtf8(String s) {
if (s == null) throw new IllegalArgumentException("s == null");
ByteString byteString = new ByteString(s.getBytes(Util.UTF_8));
byteString.utf8 = s;
return byteString;
}
/** Returns a new byte string containing the {@code charset}-encoded bytes of {@code s}. */
public static ByteString encodeString(String s, Charset charset) {
if (s == null) throw new IllegalArgumentException("s == null");
if (charset == null) throw new IllegalArgumentException("charset == null");
return new ByteString(s.getBytes(charset));
}
/** Constructs a new {@code String} by decoding the bytes as {@code UTF-8}. */
public String utf8() {
String result = utf8;
// We don't care if we double-allocate in racy code.
return result != null ? result : (utf8 = new String(data, Util.UTF_8));
}
/** Constructs a new {@code String} by decoding the bytes using {@code charset}. */
public String string(Charset charset) {
if (charset == null) throw new IllegalArgumentException("charset == null");
return new String(data, charset);
}
最后
Okio并不是設計來代替Jdk io的捉腥,但是在某些重度io的場景氓拼,如果對性能優(yōu)化追求極致的話,Okio不失是一種選擇抵碟,關于Okio還有很多細節(jié)的知識由于篇幅關系沒有涉及桃漾,有興趣的同學可以去看源碼中找答案,全文完拟逮。