Okio源碼分析

et4248 8年前發布 | 7K 次閱讀 源碼分析 鏈表 Java開發

Okio是Square公司推出的Java IO庫,也是OKHttp依賴的IO庫。今天花了兩個小時詳細研究了下。分享給大家。

老規矩,先放圖。

類圖

Okio.png

AnonymousSource類代表數據源,內部引用了InputStream。

Buffer類保存緩存數據,有個head的成員變量,指向的是以Segment為節點的鏈表的頭結點,Segment保存字節數組,是鏈表的節點類。

使用

假設我有一個test.txt文件,內容是 hello world ,現在我用Okio把它讀出來。

public static void main(String[] args) {
        File file = new File("test.txt");
        try {
            readString(new FileInputStream(file));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void readString(InputStream in) throws IOException {
      BufferedSource source = Okio.buffer(Okio.source(in));  //創建BufferedSource
      String s = source.readUtf8();  //以UTF-8讀
      System.out.println(s);     //打印
      pngSource.close();
    }

--------------------------------------輸出-----------------------------------------
hello world

Okio是對Java底層io的封裝,所以底層io能做的Okio都能做。

創建BufferedSource對象

首先調用的是Okio.source(in),我們看下Okio.source方法

public static Source source(final InputStream in) {
    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() {      //創建一個匿名Source對象,在類圖中我把它叫做AnonymousSource
      @Override public long read(Buffer sink, long byteCount) throws IOException {
        if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
        if (byteCount == 0) return 0;
        timeout.throwIfReached();
        Segment tail = sink.writableSegment(1);
        int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
        int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
        if (bytesRead == -1) return -1;
        tail.limit += bytesRead;
        sink.size += bytesRead;
        return bytesRead;
      }

      @Override public void close() throws IOException {
        in.close();
      }

      @Override public Timeout timeout() {
        return timeout;
      }

      @Override public String toString() {
        return "source(" + in + ")";
      }
    };
  }

很簡單,創建并返回一個匿名Source對象,在類圖中我把這個匿名類叫做AnonymousSource。

接著調用 Okio.buffer() ,看下源碼:

public static BufferedSource buffer(Source source) {
    if (source == null) throw new IllegalArgumentException("source == null");
    return new RealBufferedSource(source);
  }

創建一個RealBufferedSource對象,我們看下這個類

final class RealBufferedSource implements BufferedSource

核心成員變量
  public final Buffer buffer;
  public final Source source;

  public RealBufferedSource(Source source) {
    this(source, new Buffer());                //創建一個Buffer對象
  }

RealBufferedSource繼承自BufferedSource,它有兩個核心成員變量 Buffer buffe和Source source ,分別包含緩存和數據源,看下類圖就明白了。到這里,再看下類圖,AnonymousSource、RealBufferedSource,Buffer都已經創建好了。

我們仔細看下Buffer類:

public final class Buffer implements BufferedSource, BufferedSink, Cloneable

成員變量
Segment head;    //指向鏈表頭部
long size;              //字節數

調用source.readUtf8()

這個source就是上面創建的RealBufferedSource對象,看下readUtf8方法:

@Override public String readUtf8() throws IOException {
    buffer.writeAll(source);              //將數據從source讀取到緩存buffer
    return buffer.readUtf8();           //從緩存讀取數據
  }

看下buffer.writeAll方法

@Override public long writeAll(Source source) throws IOException {
    if (source == null) throw new IllegalArgumentException("source == null");
    long totalBytesRead = 0;
    for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) { //從source讀取數據
      totalBytesRead += readCount;
    }
    return totalBytesRead;
  }

該方法從source讀取數據,這個source是在Okio中創建的你們Souce對象。

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;
        timeout.throwIfReached();
        Segment tail = sink.writableSegment(1);   //從buffer獲取尾節點
        int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
        int bytesRead = in.read(tail.data, tail.limit, maxToCopy);  //從InputStream讀取數據到Buffer中的鏈表的尾節點
        if (bytesRead == -1) return -1;
        tail.limit += bytesRead;
        sink.size += bytesRead;
        return bytesRead;
      }

      @Override public void close() throws IOException {
        in.close();
      }

      @Override public Timeout timeout() {
        return timeout;
      }

      @Override public String toString() {
        return "source(" + in + ")";
      }
    };

好了,到這里InputStream的數據就被保存到了Buffer中的鏈表中了。

接著應該調用RealBufferedSource.readUtf8()中的buffer.readUtf8()方法,看下源碼:

@Override public String readUtf8() {
    try {
      return readString(size, Util.UTF_8);
    } catch (EOFException e) {
      throw new AssertionError(e);
    }
  }
  @Override public String readString(Charset charset) {
    try {
      return readString(size, charset);
    } catch (EOFException e) {
      throw new AssertionError(e);
    }
  }

  @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);//頭節點的字節夠我們讀,直接用當前Segment的字節數據構造String對象
    s.pos += byteCount;
    size -= byteCount;

    if (s.pos == s.limit) {
      head = s.pop();
      SegmentPool.recycle(s);
    }

    return result;
  }

最終會從Buffer的鏈表中的頭節點開始讀取字節,如果頭結點的字節數,不夠我們讀,接著讀鏈表的下一個節點。如果頭節點的字節夠我們讀,直接用當前Segment的字節數據構造String對象。好了,一次讀取String的過程結束了。再去看看類圖,肯定清楚了。

總結

調有Okio的source方法會返回一個實現了Source接口的對象,這個對象引用了InputStream,所以它就代表了數據源。

Buffer保存從source讀取的字節,真正存儲字節在Segment對象中,Buffer保存著以Segment為節點的鏈表的頭結點,所以Buffer可以獲取所有數據。

 

 

 

來自:http://www.jianshu.com/p/ccf24a63dca8

 

 本文由用戶 et4248 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!