天天看點

Okio簡化處理IO操作

Okio庫是一個由square公司開發的,它補充了Java.io和java.nio的不足,以便能夠更加友善,快速的通路、存儲和處理你的資料。而OkHttp的底層也使用該庫作為支援。

Okio底層通過兩種包含多種能力的類提供豐富的API。它們是ByteStrings 和 Buffers。

ByteStrings

 這個類提供了豐富的編碼和解碼操作,比如hex, base64, and UTF-8。

Buffers

 它提供了位元組緩沖區操作,類似隊列結果,從頭部讀取資料,寫入的資料添加到尾部。

   由于内部高效的算法,ByteStrings和Buffer很大程度上節約了CPU資源和記憶體。 

Okio有自己的流類型,叫做Source和Sink。Source類似java io中的InputStream,Sink類似java io中的OutputStream,但是又有一些關鍵的不同點:

1、Timeouts

讀寫提供了逾時檢查

2、接口實作很簡單

Source隻定義了三個方法:read(),close(),timeout().

3、使用更加簡單

BufferedSource和BufferedSink基本上提供了你需要的任何功能

Okio中有兩個關鍵的接口,Sink和Source,這兩個接口都繼承了Closeable接口;而Sink可以簡單的看做OutputStream,Source可以簡單的看做InputStream。而這兩個接口都是支援讀寫逾時設定的。結構圖如下

Okio簡化處理IO操作

它們各自有一個支援緩沖區的子類接口,BufferedSink和BufferedSource,而BufferedSink有一個實作類RealBufferedSink,BufferedSource有一個實作類RealBufferedSource;此外,Sink和Source它門還各自有一個支援gzip壓縮的實作類GzipSink和GzipSource;一個具有委托功能的抽象類ForwardingSink和ForwardingSource;還有一個實作類便是InflaterSource和DeflaterSink,這兩個類主要用于壓縮,為GzipSink和GzipSource服務;整體的結構圖如下

Okio簡化處理IO操作

BufferedSink中定義了一系列寫入緩存區的方法,比如write方法寫byte數組,writeUtf8寫字元串,還有一些列的writeByte,writeString,writeShort,writeInt,writeLong,writeDecimalLong等等方法;

BufferedSource定義的方法和BufferedSink極為相似,隻不過一個是寫一個是讀,基本上都是一一對應的,如readUtf8,readByte,readString,readShort,readInt等等等等。這兩個接口中的方法有興趣的點源碼進去看就可以了。

而這兩個支援緩沖區的接口的實作類RealBufferedSink和RealBufferedSource都是通過包裝一個Sink+Buffer或者Source+Buffer來進行實作的。如下圖所示

Okio簡化處理IO操作

拿RealBufferedSink來舉例,實際調用的write的一系列方法,都是直接的對成員變量buffer進行的操作,當寫入buffer成功後,最後會調用一個方法将buffer中的内容寫入到sink中去,我們随便拿一個方法看一下

@Override public BufferedSink writeShort(int s) throws IOException {
if (closed) throw new IllegalStateException("closed");
buffer.writeShort(s);
return emitCompleteSegments();
}
           

可以看到,首先會判斷closed成員變量是否是标記着關閉,如果已經關閉了則扔出一個異常,否則将内容寫入到buffer,寫入完成後調用了一個emitCompleteSegments的方法,該方法中做了什麼呢

@Override public BufferedSink emitCompleteSegments() throws IOException {
if (closed) throw new IllegalStateException("closed");
long byteCount = buffer.completeSegmentByteCount();
if (byteCount > 0) sink.write(buffer, byteCount);
return this;
}
           

這兩個實作類的内部的所有方法都是類似的,這裡不一一展開。

而這一切的背後都是一個叫做Buffer的類在支援着緩沖區,Buffer是BufferedSink和BufferedSource的實作類,是以它既可以用來讀資料,也可以用來寫資料,其内部使用了一個Segment和SegmentPool,維持着一個連結清單,其循環利用的機制和Android中Message的利用機制是一模一樣的。

/**
 * A collection of unused segments, necessary to avoid GC churn and zero-fill.
 * This pool is a thread-safe static singleton.
 */
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 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;
    }
  }
}
           

内部一個成員變量next指向連結清單下一個元素,take方法首先判斷池中是否存在可用的,存在則傳回,不存在則new一個,而recycle則是将不再使用的Segment重新扔到池中去。進而達到一個Segment池的作用。

而Okio暴露給外部使用的類便是Okio這個類,其内部有大量的靜态方法,包括通過一個Source獲得BufferedSource,通過一個Sink獲得一個BufferedSink。這個過程很簡單,我們調用Okio的buffer方法即可傳回我們需要的,如下

Okio.buffer(source);
Okio.buffer(sink);
           

但是上面兩個方法需要傳遞一個Sink或者Source,那麼這個Sink和Source又是如何獲得的呢。其實方法也在Okio這個類中。我們可以調用sink方法獲得一個Sink,調用source方法獲得一個Source,而資料的來源或者目的可以是一個File,一個輸入或者輸出流,一個Socket連結等等。如下

Okio.sink(File file);
Okio.appendingSink(File file);//内容可追加
Okio.sink(Path path, OpenOption... options);
Okio.sink(OutputStream out);
Okio.sink(Socket socket);

Okio.source(File file);
Okio.source(Path path, OpenOption... options);
Okio.source(InputStream in);
Okio.source(Socket socket);
           

下面舉兩個案例來簡單說明如何使用Okio。

1、檔案讀寫

//寫入資料到檔案中
Sink sink = Okio.sink(file);
BufferedSink bufferedSink = Okio.buffer(sink);
bufferedSink.writeUtf8("我是一個中國人");
bufferedSink.close();

//從檔案中讀取資料
Source source = Okio.source(file);
BufferedSource bufferedSource = Okio.buffer(source);
String data = bufferedSource.readUtf8();
bufferedSource.close();
           

2、Gzip壓縮和讀取

//zip壓縮
GzipSink gzipSink = new GzipSink(Okio.sink(file));
BufferedSink bufferedSink = Okio.buffer(gzipSink);
bufferedSink.writeUtf8("this is zip file");
bufferedSink.flush();
bufferedSink.close();

//讀取zip
GzipSource gzipSource = new GzipSource(Okio.source(file));
BufferedSource bufferedSource = Okio.buffer(gzipSource);
String s = bufferedSource.readUtf8();
bufferedSource.close();
           

繼續閱讀