Okio筆記

Okio筆記

一、基本認(rèn)識(shí)

Okio庫(kù)是一個(gè)由square公司開(kāi)發(fā)的,它補(bǔ)充了java.io和java.nio的不足,以便能夠更加方便,快速地訪問(wèn)、存儲(chǔ)和處理數(shù)據(jù)。而OkHttp的底層也使用該庫(kù)作為支持。而在開(kāi)發(fā)中,使用該庫(kù)可以大大的帶來(lái)方便。

Okio中有兩個(gè)關(guān)鍵的接口,SinkSource,這兩個(gè)接口都繼承了Closeable接口;而Sink可以簡(jiǎn)單的看做OutputStream,Source可以簡(jiǎn)單的看做InputStream,這兩個(gè)接口都是支持讀寫(xiě)超時(shí)設(shè)置的。

Okio相關(guān)類(lèi)

它們各自有一個(gè)支持緩沖區(qū)的子類(lèi)接口,BufferedSinkBufferedSource,而B(niǎo)ufferedSink有一個(gè)實(shí)現(xiàn)類(lèi)RealBufferedSink,BufferedSource有一個(gè)實(shí)現(xiàn)類(lèi)RealBufferedSource;此外,Sink和Source它門(mén)還各自有一個(gè)支持gzip壓縮的實(shí)現(xiàn)類(lèi)GzipSinkGzipSource;一個(gè)具有委托功能的抽象類(lèi)ForwardingSinkForwardingSource;還有一個(gè)實(shí)現(xiàn)類(lèi)便是InflaterSourceDeflaterSink,這兩個(gè)類(lèi)主要用于壓縮,為GzipSink和GzipSource服務(wù);

BufferedSink中定義了一系列寫(xiě)入緩存區(qū)的方法,比如write方法寫(xiě)byte數(shù)組,writeUtf8寫(xiě)字符串,還有一些列的writeByte,writeString,writeShort,writeInt,writeLong,writeDecimalLong等等方法。BufferedSource定義的方法和BufferedSink極為相似,只不過(guò)一個(gè)是寫(xiě)一個(gè)是讀,基本上都是一一對(duì)應(yīng)的,如readUtf8,readByte,readString,readShort,readInt等等等等。這兩個(gè)接口中的方法有興趣的點(diǎn)源碼進(jìn)去看就可以了。

二、簡(jiǎn)單使用

//簡(jiǎn)單的文件讀寫(xiě)
public void readWriteFile() throws Exception {
    //1.文件
    File file = new File("resources/dest.txt");
    //2.構(gòu)建寫(xiě)緩沖池
    BufferedSink sink = Okio.buffer(Okio.sink(file));
    //3.向緩沖池寫(xiě)入文本
    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)建寫(xiě)緩沖池
    BufferedSink sink = Okio.buffer(Okio.appendingSink(file));
    //2.追加文本
    sink.writeUtf8("Hello, ");
    //3.關(guān)閉
    sink.close();

    //4.再次追加文本,需要重新構(gòu)建緩沖池對(duì)象
    sink = Okio.buffer(Okio.appendingSink(file));
    //5.追加文本
    sink.writeUtf8("java.io file!");
    //6.關(guān)閉緩沖池
    sink.close();
}

//通過(guò)路徑來(lái)讀寫(xiě)文件
public void readWritePath() throws Exception {
    Path path = new File("resources/dest.txt").toPath();
    //1.構(gòu)建寫(xiě)緩沖池
    BufferedSink sink = Okio.buffer(Okio.sink(path));
    //2.寫(xiě)緩沖
    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();
}

//寫(xiě)B(tài)uffer,在okio中Buffer是一個(gè)很重要的對(duì)象,在后面我們?cè)谠敿?xì)介紹。
public void sinkFromOutputStream() throws Exception {
    //1.構(gòu)建buffer對(duì)象
    Buffer data = new Buffer();
    //2.向緩沖中寫(xiě)入文本
    data.writeUtf8("a");
    //3.可以連續(xù)追加,類(lèi)似StringBuffer
    data.writeUtf8("c");

    //4.構(gòu)建字節(jié)數(shù)組流對(duì)象
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    //5.構(gòu)建寫(xiě)緩沖池
    Sink sink = Okio.sink(out);
    //6.向池中寫(xiě)入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的文件讀寫(xiě)操作,使用起來(lái)是很簡(jiǎn)單的,減少了很多io操作的基本代碼,并且對(duì)內(nèi)存和cpu使用做了優(yōu)化(代碼直觀當(dāng)然是看不出來(lái)的,okio作者在buffer類(lèi)中有詳細(xì)的說(shuō)明,后面我們?cè)俜治鲞@個(gè)類(lèi))。

此外還有一個(gè)ByteString類(lèi),這個(gè)類(lèi)可以用來(lái)做各種變化,它將byte轉(zhuǎn)會(huì)為String,而這個(gè)String可以是utf8的值,也可以是base64后的值,也可以是md5的值,也可以是sha256的值,總之就是各種變化,最后取得你想要的值。

三、Okio框架結(jié)構(gòu)與源碼分析

Okio框架分析

Okio的簡(jiǎn)單使用,無(wú)非下面幾個(gè)步驟:
a. 構(gòu)建對(duì)象
b. 讀、寫(xiě)
c. 關(guān)閉緩沖對(duì)象

構(gòu)建緩沖對(duì)象

通過(guò)Okio的buffer(Source source)buffer(Sink sink)方法,構(gòu)建緩沖對(duì)象

public static BufferedSource buffer(Source source) {
    return new RealBufferedSource(source);
}
  
public static BufferedSink buffer(Sink sink) {
    return new RealBufferedSink(sink);
}

這兩個(gè)static方法,返回 讀、寫(xiě)池:BufferedSource 、BufferedSink。Source、Sink ,這兩種參數(shù)從哪里來(lái)?看看Okio下面兩個(gè)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));
}

類(lèi)似的方法還有

  • 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了一個(gè)Sink對(duì)象,這個(gè)對(duì)象就是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);//真正完成寫(xiě)操作!

          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 + ")";
      }
    };
  }
讀、寫(xiě)操作

下面以寫(xiě)操作為例,來(lái)查看源碼,在RealBufferedSink

@Override 
public BufferedSink writeUtf8(String string, int beginIndex, int endIndex)
      throws IOException {
    if (closed) throw new IllegalStateException("closed");
    buffer.writeUtf8(string, beginIndex, endIndex);//寫(xiě)入到buffer中
    return emitCompleteSegments();//將buffer中的內(nèi)容寫(xiě)入到sink成員變量中去,然后將自身返回
}

可以看到這里,有一個(gè)成員變量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) {
    //返回一個(gè)可寫(xiě)的Segment,可以使用的capacity至少為1(一個(gè)Segment的總大小為8KB),若當(dāng)前Segment已經(jīng)寫(xiě)滿(mǎn)了,則會(huì)新建一個(gè)Segment返回
    Segment tail = writableSegment(1);
    tail.data[tail.limit++] = (byte) b;//將入?yún)⒎诺絊egment中
    size += 1;//總長(zhǎng)度+1
    return this;
  }

這一切的背后都是一個(gè)叫做Buffer的類(lèi)在支持著緩沖區(qū),Buffer是BufferedSink和BufferedSource的實(shí)現(xiàn)類(lèi),因此它既可以用來(lái)讀數(shù)據(jù),也可以用來(lái)寫(xiě)數(shù)據(jù),其內(nèi)部使用了一個(gè)Segment和SegmentPool,維持著一個(gè)鏈表,其循環(huán)利用的機(jī)制和Android中Message的利用機(jī)制是一模一樣的。

final class SegmentPool {//這是一個(gè)鏈表,Segment是其元素,總大小為8KB
  static final long MAX_SIZE = 64 * 1024; // 64 KiB.

  static Segment next;//指向鏈表的下一個(gè)元素

  static long byteCount;

  private SegmentPool() {
  }

  static Segment take() {
    synchronized (SegmentPool.class) {
      if (next != null) {//從鏈表中取出一個(gè)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)部一個(gè)成員變量next指向鏈表下一個(gè)元素,take方法首先判斷池中是否存在可用的,存在則返回,不存在則new一個(gè),而recycle則是將不再使用的Segment重新扔到池中去。從而達(dá)到一個(gè)Segment池的作用。

回到writeUtf8方法中,RealBufferedSink的emitCompleteSegments()方法完成寫(xiě)的提交

@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);//這個(gè)sink就是剛才new的
    return this;
}

Okio的寫(xiě)操作流程圖

參考文獻(xiàn)

Android 善用Okio簡(jiǎn)化處理I/O操作
Android Okhttp之Okio解析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容