從JDK源碼看InputStream

概況

JDK 給咱們提供了不少實用的輸入流 xxxInputStream,而 InputStream 是全部字節輸入流的抽象。包括 ByteArrayInputStream 、FilterInputStream 、BufferedInputStream 、DataInputStream 和 PushbackInputStream 等等。html

繼承結構

--java.lang.Object
  --java.io.InputStream複製代碼

類定義

public abstract class InputStream implements Closeable複製代碼

InputStream 被定爲 public 且 abstract 的類,實現了Closeable接口。java

Closeable 接口表示 InputStream 能夠被close,接口定義以下:數組

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

主要屬性

private static final int MAX_SKIP_BUFFER_SIZE = 2048;

private static final int DEFAULT_BUFFER_SIZE = 8192;

private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;複製代碼
  • MAX_SKIP_BUFFER_SIZE 表示輸入流每次最多能跳過的字節數。
  • DEFAULT_BUFFER_SIZE 默認的緩衝大小。
  • MAX_BUFFER_SIZE 表示最大的緩衝數組大小,這裏設置爲 Integer.MAX_VALUE - 8 這裏也是考慮到 JVM 能支持的大小,超過這個值就會致使 OutOfMemoryError。

主要方法

read方法

一共有三個 read 方法,其中有一個抽象的 read 方法,其他兩個 read 方法都會調用這個抽象方法,該方法用於從輸入流讀取下一個字節,返回一個0到255範圍的值。若是已經到達輸入流結尾處而致使無可讀字節則返回-1,同時,此方法爲阻塞方法,解除阻塞的條件:bash

  1. 有可讀的字節。
  2. 檢測到已是輸入流的結尾了。
  3. 拋出異常。

主要看第三個 read 方法便可,它傳入的三個參數,byte數組、偏移量和數組長度。該方法主要是從輸入流中讀取指定長度的字節數據到字節數組中,須要注意的是這裏只是嘗試去讀取長度爲 len 的數組,但真正讀取到的數組長度不必定爲 len,返回值纔是真正讀取到的長度。併發

public abstract int read() throws IOException;

    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }複製代碼

看看它的邏輯,數組爲null則拋空指針,偏移量和長度超過邊界也拋異常,長度爲0則什麼都不敢直接返回0。接着調用 read() 讀取一個字節,若是爲-1則說明結束,直接返回-1。不然繼續根據數組長度循環調用 read() 方法讀取字節,而且填充到傳入的數組對象中,最後返回讀取的字節數。優化

readAllBytes方法

該方法從輸入流讀取全部剩餘的字節,在此過程是阻塞的,直到全部剩餘字節都被讀取或到達流的結尾或發生異常。ui

邏輯是用一個 for 循環內嵌一個 while 循環,while 循環不斷調用 read 方法嘗試將 DEFAULT_BUFFER_SIZE 長度的字節數組填滿,一旦填滿則須要將數組容量擴容一倍,再將原字節數組複製到新數組中,而後再經過 while 循環繼續讀取,直到達到尾部才跳出 for 循環,最後返回讀取到的全部字節數組。this

public byte[] readAllBytes() throws IOException {
        byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
        int capacity = buf.length;
        int nread = 0;
        int n;
        for (;;) {
            while ((n = read(buf, nread, capacity - nread)) > 0)
                nread += n;
            if (n < 0)
                break;
            if (capacity <= MAX_BUFFER_SIZE - capacity) {
                capacity = capacity << 1;
            } else {
                if (capacity == MAX_BUFFER_SIZE)
                    throw new OutOfMemoryError("Required array size too large");
                capacity = MAX_BUFFER_SIZE;
            }
            buf = Arrays.copyOf(buf, capacity);
        }
        return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
    }複製代碼

readNBytes方法

從輸入流中讀取指定長度的字節,並且它能保證必定能讀取到指定的長度,它屬於阻塞方式,用一個 while 循環不斷調用 read 讀取字節,直到讀取到指定長度才結束讀取。spa

public int readNBytes(byte[] b, int off, int len) throws IOException {
        Objects.requireNonNull(b);
        if (off < 0 || len < 0 || len > b.length - off)
            throw new IndexOutOfBoundsException();
        int n = 0;
        while (n < len) {
            int count = read(b, off + n, len - n);
            if (count < 0)
                break;
            n += count;
        }
        return n;
    }複製代碼

available方法

返回從該輸入流能進行非阻塞讀取的剩餘字節數,當調用 read 讀取的字節數通常會小於該值,有一些InputStream的子實現類會經過該方法返回流的剩餘總字節數,但有些並不會,因此使用時要注意點。.net

這裏抽象類直接返回0,子類中重寫該方法。

public int available() throws IOException {
        return 0;
    }複製代碼

skip方法

從輸入流中跳過指定個數字節,返回值爲真正跳過的個數。這裏的實現是簡單經過不斷調用 read 方法來實現跳過邏輯,但這是較低效的,子類可用更高效的方式重寫此方法。

下面看看邏輯,最大的跳過長度不能超過 MAX_SKIP_BUFFER_SIZE ,而且用一個 while 循環調用 read 方法,若是遇到返回爲-1,即已經到達結尾了,則跳出循環。能夠看到 skipBuffer 實際上是沒有什麼做用,直接讓其被 GC 便可,最後返回真正跳過的字節數。

public long skip(long n) throws IOException {

        long remaining = n;
        int nr;

        if (n <= 0) {
            return 0;
        }

        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {
                break;
            }
            remaining -= nr;
        }

        return n - remaining;
    }複製代碼

close方法

此方法用於關閉輸入流,而且釋放相關資源 。

public void close() throws IOException {}複製代碼

transferTo方法

從輸入流中按順序讀取所有字節而且寫入到指定的輸出流中,返回值爲轉移的字節數。轉移過程當中可能會發生不肯定次的阻塞,阻塞可能發生在 read 操做或 write 操做。

主要邏輯是用 while 循環不斷調用 read 方法操做讀取字節,而後調用輸出流的 write 方法寫入,直到讀取返回-1,即達到結尾。最後返回轉移的字節數。

public long transferTo(OutputStream out) throws IOException {
        Objects.requireNonNull(out, "out");
        long transferred = 0;
        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
        int read;
        while ((read = this.read(buffer, 0, DEFAULT_BUFFER_SIZE)) >= 0) {
            out.write(buffer, 0, read);
            transferred += read;
        }
        return transferred;
    }複製代碼

markSupported方法

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

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

mark方法

標記輸入流當前位置,與之對應的是 reset 方法,經過他們之間的組合能實現重複讀取操做。另外它會傳入 readlimit 參數,它用於表示該輸入流中在執行 mark 操做後最多能夠讀 readlimit 個字節後才使 mark 的位置失效。

能夠看到 InputStream 的 mark 方法是什麼都不作的,子類中再具體實現。

public synchronized void mark(int readlimit) {}複製代碼

reset方法

與 mark 方法對應,它能夠重置輸入流的位置到上次被 mark 操做標識的位置。InputStream 的 reset 方法直接拋出一個 IOException,子類中根據實際狀況實現。

public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }複製代碼

如下是廣告相關閱讀

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

鄙人的新書《Tomcat內核設計剖析》已經在京東銷售了,有須要的朋友能夠到 item.jd.com/12185360.ht… 進行預約。感謝各位朋友。

爲何寫《Tomcat內核設計剖析》

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

相關閱讀:

從JDK源碼角度看Object
從JDK源碼角度看Long
從JDK源碼角度看Integer
volatile足以保證數據同步嗎
談談Java基礎數據類型
從JDK源碼角度看併發鎖的優化
從JDK源碼角度看線程的阻塞和喚醒
從JDK源碼角度看併發競爭的超時
從JDK源碼角度看java併發線程的中斷
從JDK源碼角度看Java併發的公平性
從JDK源碼角度看java併發的原子性如何保證
從JDK源碼看Writer
從JDK源碼看關閉鉤子
從JDK源碼角度看Byte
從JDK源碼角度看Boolean
從JDK源碼角度看Short

歡迎關注:

這裏寫圖片描述
這裏寫圖片描述

這裏寫圖片描述
這裏寫圖片描述
相關文章
相關標籤/搜索