Okio筆記

Okio筆記

一睦番、基本認(rèn)識

Okio庫是一個由square公司開發(fā)的喜爷,它補(bǔ)充了java.io和java.nio的不足当窗,以便能夠更加方便够坐,快速地訪問、存儲和處理數(shù)據(jù)。而OkHttp的底層也使用該庫作為支持元咙。而在開發(fā)中梯影,使用該庫可以大大的帶來方便。

Okio中有兩個關(guān)鍵的接口庶香,SinkSource光酣,這兩個接口都繼承了Closeable接口;而Sink可以簡單的看做OutputStream脉课,Source可以簡單的看做InputStream救军,這兩個接口都是支持讀寫超時設(shè)置的。

Okio相關(guān)類

它們各自有一個支持緩沖區(qū)的子類接口倘零,BufferedSinkBufferedSource唱遭,而BufferedSink有一個實(shí)現(xiàn)類RealBufferedSink,BufferedSource有一個實(shí)現(xiàn)類RealBufferedSource呈驶;此外拷泽,Sink和Source它門還各自有一個支持gzip壓縮的實(shí)現(xiàn)類GzipSinkGzipSource;一個具有委托功能的抽象類ForwardingSinkForwardingSource袖瞻;還有一個實(shí)現(xiàn)類便是InflaterSourceDeflaterSink司致,這兩個類主要用于壓縮,為GzipSink和GzipSource服務(wù)聋迎;

BufferedSink中定義了一系列寫入緩存區(qū)的方法脂矫,比如write方法寫byte數(shù)組,writeUtf8寫字符串霉晕,還有一些列的writeByte庭再,writeString,writeShort牺堰,writeInt拄轻,writeLong,writeDecimalLong等等方法伟葫。BufferedSource定義的方法和BufferedSink極為相似恨搓,只不過一個是寫一個是讀,基本上都是一一對應(yīng)的筏养,如readUtf8斧抱,readByte,readString撼玄,readShort夺姑,readInt等等等等墩邀。這兩個接口中的方法有興趣的點(diǎn)源碼進(jìn)去看就可以了掌猛。

二、簡單使用

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

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

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

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

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

此外還有一個ByteString類鸠匀,這個類可以用來做各種變化蕉斜,它將byte轉(zhuǎn)會為String,而這個String可以是utf8的值缀棍,也可以是base64后的值宅此,也可以是md5的值,也可以是sha256的值爬范,總之就是各種變化父腕,最后取得你想要的值。

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

Okio框架分析

Okio的簡單使用侣诵,無非下面幾個步驟:
a. 構(gòu)建對象
b. 讀、寫
c. 關(guān)閉緩沖對象

構(gòu)建緩沖對象

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

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 搭儒,這兩種參數(shù)從哪里來?看看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方法為例穷当,最后都是調(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了一個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中的內(nèi)容寫入到sink成員變量中去汪疮,然后將自身返回
}

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

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

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;
    }
  }
}

內(nèi)部一個成員變量next指向鏈表下一個元素腕让,take方法首先判斷池中是否存在可用的孤钦,存在則返回,不存在則new一個纯丸,而recycle則是將不再使用的Segment重新扔到池中去偏形。從而達(dá)到一個Segment池的作用。

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

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

Okio的寫操作流程圖

參考文獻(xiàn)

Android 善用Okio簡化處理I/O操作
Android Okhttp之Okio解析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俊扭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子坠陈,更是在濱河造成了極大的恐慌萨惑,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仇矾,死亡現(xiàn)場離奇詭異庸蔼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)贮匕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門姐仅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人刻盐,你說我怎么就攤上這事掏膏。” “怎么了敦锌?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵馒疹,是天一觀的道長。 經(jīng)常有香客問我乙墙,道長颖变,這世上最難降的妖魔是什么生均? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮悼做,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘哗魂。我一直安慰自己肛走,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布录别。 她就那樣靜靜地躺著朽色,像睡著了一般。 火紅的嫁衣襯著肌膚如雪组题。 梳的紋絲不亂的頭發(fā)上葫男,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機(jī)與錄音崔列,去河邊找鬼梢褐。 笑死,一個胖子當(dāng)著我的面吹牛赵讯,可吹牛的內(nèi)容都是我干的盈咳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼边翼,長吁一口氣:“原來是場噩夢啊……” “哼鱼响!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起组底,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤丈积,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后债鸡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體江滨,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了干奢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袜啃。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖间雀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情镊屎,我是刑警寧澤惹挟,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站缝驳,受9級特大地震影響连锯,放射性物質(zhì)發(fā)生泄漏归苍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一运怖、第九天 我趴在偏房一處隱蔽的房頂上張望拼弃。 院中可真熱鬧,春花似錦摇展、人聲如沸吻氧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盯孙。三九已至,卻和暖如春祟滴,著一層夾襖步出監(jiān)牢的瞬間振惰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工垄懂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骑晶,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓草慧,卻偏偏與公主長得像透罢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子冠蒋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容