從JDK原始碼看Reader

超人汪小建發表於2019-02-25

概況

Reader 是一個用於讀字元流的抽象類,它將一些相通的讀相關操作抽象到此類,方便各種讀操作類的實現。一般來說子類只需要實現它的 read 和 close 兩個方法,但如果有需要還可以重寫 Reader 提供的公共方法。

JDK 在 Reader 的基礎上實現了很多有用的 xxxReader ,包括 BufferedReader、CharArrayReader、FilterReader、InputStreamReader、FileReader、PipedReader、StringReader 和 LineNumberReader 等等。

繼承結構

--java.lang.Object
  --java.io.Reader
複製程式碼

類定義

public abstract class Reader implements Readable, Closeable
複製程式碼

Reader 被定為 public 且 abstract 的類,實現了 Readable 和 Closeable介面。

Readable 介面表示嘗試將字元讀取到指定的緩衝中,介面定義如下:

public interface Readable {
    public int read(java.nio.CharBuffer cb) throws IOException;
}
複製程式碼

Closeable 介面表示 Reader 可以被close,介面定義如下:

public interface Closeable extends AutoCloseable {
    public void close() throws IOException;
}
複製程式碼

主要屬性

private static final int maxSkipBufferSize = 8192;

private char skipBuffer[] = null;

protected Object lock;
複製程式碼
  • maxSkipBufferSize 最大跳過緩衝的大小。
  • skipBuffer 是一個 char[] 型別,表示跳過緩衝。
  • lock 是 Reader 的鎖,用於實現同步。

構造方法

有兩種構造方法,不帶引數時則將自己作為鎖,而如果傳入了某個 Object 物件則將其作為鎖。

protected Reader() {
    this.lock = this;
}
protected Reader(Object lock) {
    if (lock == null) {
        throw new NullPointerException();
    }
    this.lock = lock;
}
複製程式碼

主要方法

read方法

一共有四個 read 方法,其中有一個抽象的 read 方法,可以看到所有 read 方法最終都會呼叫這個抽象方法,提供給子類處理邏輯的實現。它傳入的三個引數,字元陣列cbuf、偏移量off和陣列長度。

public abstract int read(char cbuf[], int off, int len) throws IOException;
複製程式碼

無參的 read 方法其實是預設讀一個字元,new 一個 char 物件然後呼叫子類實現進行讀取,最後返回讀到的字元。

public int read() throws IOException {
    char cb[] = new char[1];
    if (read(cb, 0, 1) == -1)
        return -1;
    else
        return cb[0];
}
    
複製程式碼

假如 read 方法傳入的引數為 char 陣列時,則直接呼叫子類實現進行讀取。

public int read(char cbuf[]) throws IOException {
    return read(cbuf, 0, cbuf.length);
}
複製程式碼

最後一個 read 方法其實是 Readable 介面定義的方法,用於讀取字元到指定的 CharBuffer 物件中,邏輯是先得到 CharBuffer 物件剩餘長度,根據該長度例項化 char 陣列,然後呼叫子類實現完成讀取,最後將讀取到的字元放進 CharBuffer 物件。

public int read(java.nio.CharBuffer target) throws IOException {
    int len = target.remaining();
    char[] cbuf = new char[len];
    int n = read(cbuf, 0, len);
    if (n > 0)
        target.put(cbuf, 0, n);
    return n;
}
複製程式碼

ready方法

表示該讀取器是否已準備好,預設返回 false,如果能保證呼叫 read 方法讀取下一個字元不阻塞則返回 true。

public boolean ready() throws IOException {
    return false;
}
複製程式碼

skip方法

該方法用於跳過指定長度字元,期間如果某些字元還未可讀則可能發生阻塞,另外期間如果發生 IO 錯誤則會拋異常。邏輯是,

  1. 跳過字元長度不能小於0。
  2. 跳過長度不能超過最大跳過長度 maxSkipBufferSize,超過則只能取 maxSkipBufferSize。
  3. 加鎖開始處理,skipBuffer 物件如果為空則需要先例項化指定長度的 char 陣列。
  4. 不斷迴圈呼叫子類的 read 方法進行讀取,對讀取到的字元不做處理,即實現了跳過效果。
public long skip(long n) throws IOException {
    if (n < 0L)
        throw new IllegalArgumentException("skip value is negative");
    int nn = (int) Math.min(n, maxSkipBufferSize);
    synchronized (lock) {
        if ((skipBuffer == null) || (skipBuffer.length < nn))
            skipBuffer = new char[nn];
        long r = n;
        while (r > 0) {
            int nc = read(skipBuffer, 0, (int)Math.min(r, nn));
            if (nc == -1)
                break;
            r -= nc;
        }
        return n - r;
    }
}
複製程式碼

close方法

它是一個抽象方法,留給子類實現。此方法用於關閉流,並且釋放相關的資源 。關閉後再呼叫 read()、ready()、mark()、reset()或skip()等方法將丟擲 IO 異常。

public abstract void close() throws IOException;
複製程式碼

markSupported方法

是否支援 mark 和 reset 操作,這裡直接返回 false,子類根據實際重寫該方法。

public boolean markSupported() {
    return false;
}
複製程式碼

mark方法

標記讀取器當前的位置,與之對應的是 reset 方法,通過他們之間的組合能實現重複讀取操作。預設是不支援此操作的,需要子類重寫該方法。

public void mark(int readAheadLimit) throws IOException {
    throw new IOException("mark() not supported");
}
複製程式碼

reset方法

與 mark 方法對應,它可以重置讀取器的位置到上次被 mark 操作標識的位置,如果未被標記過則可能會被重置到開始的位置。預設是不支援此操作的,需要子類重寫該方法。

public void reset() throws IOException {
    throw new IOException("reset() not supported");
}
複製程式碼

=============廣告時間===============

公眾號的選單已分為“分散式”、“機器學習”、“深度學習”、“NLP”、“Java深度”、“Java併發核心”、“JDK原始碼”、“Tomcat核心”等,可能有一款適合你的胃口。

鄙人的新書《Tomcat核心設計剖析》已經在京東銷售了,有需要的朋友可以購買。感謝各位朋友。

為什麼寫《Tomcat核心設計剖析》

=========================

歡迎關注:

這裡寫圖片描述

相關文章