第3章 IO

Okio筆記

一恨旱、基本認(rèn)識

Okio庫是一個由square公司開發(fā)的,它補充了java.io和java.nio的不足坝疼,以便能夠更加方便搜贤,快速地訪問、存儲和處理數(shù)據(jù)钝凶。而OkHttp的底層也使用該庫作為支持仪芒。而在開發(fā)中,使用該庫可以大大的帶來方便耕陷。

Okio中有兩個關(guān)鍵的接口掂名,SinkSource,這兩個接口都繼承了Closeable接口哟沫;而Sink可以簡單的看做OutputStream饺蔑,Source可以簡單的看做InputStream,這兩個接口都是支持讀寫超時設(shè)置的嗜诀。

Okio相關(guān)類

它們各自有一個支持緩沖區(qū)的子類接口猾警,BufferedSinkBufferedSource,而BufferedSink有一個實現(xiàn)類RealBufferedSink裹虫,BufferedSource有一個實現(xiàn)類RealBufferedSource肿嘲;此外,Sink和Source它門還各自有一個支持gzip壓縮的實現(xiàn)類GzipSinkGzipSource筑公;一個具有委托功能的抽象類ForwardingSinkForwardingSource雳窟;還有一個實現(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等等等等。這兩個接口中的方法有興趣的點源碼進去看就可以了兰绣。

二世分、簡單使用

//簡單的文件讀寫
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是一個很重要的對象,在后面我們在詳細介紹雌澄。
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類中有詳細的說明,后面我們再分析這個類)旗唁。

此外還有一個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的實現(xiàn)類,因此它既可以用來讀數(shù)據(jù)尚卫,也可以用來寫數(shù)據(jù)归榕,其內(nèi)部使用了一個Segment和SegmentPool,維持著一個鏈表吱涉,其循環(huán)利用的機制和Android中Message的利用機制是一模一樣的刹泄。

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重新扔到池中去。從而達到一個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的寫操作流程圖
image
image

參考文獻

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贼穆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子兰粉,更是在濱河造成了極大的恐慌故痊,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玖姑,死亡現(xiàn)場離奇詭異愕秫,居然都是意外死亡,警方通過查閱死者的電腦和手機焰络,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門戴甩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人闪彼,你說我怎么就攤上這事甜孤。” “怎么了畏腕?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵缴川,是天一觀的道長。 經(jīng)常有香客問我描馅,道長把夸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任铭污,我火速辦了婚禮恋日,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嘹狞。我一直安慰自己岂膳,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布磅网。 她就那樣靜靜地躺著闷营,像睡著了一般。 火紅的嫁衣襯著肌膚如雪知市。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天速蕊,我揣著相機與錄音嫂丙,去河邊找鬼。 笑死规哲,一個胖子當(dāng)著我的面吹牛跟啤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼隅肥,長吁一口氣:“原來是場噩夢啊……” “哼竿奏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起腥放,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤泛啸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后秃症,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體候址,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年樟澜,在試婚紗的時候發(fā)現(xiàn)自己被綠了扭倾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辈双。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖荠雕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情驶赏,我是刑警寧澤炸卑,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站母市,受9級特大地震影響矾兜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜患久,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一椅寺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蒋失,春花似錦返帕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至铣卡,卻和暖如春链韭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背煮落。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工敞峭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蝉仇。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓旋讹,卻偏偏與公主長得像殖蚕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子沉迹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,700評論 2 345

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