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;複製代碼
一共有三個 read 方法,其中有一個抽象的 read 方法,其他兩個 read 方法都會調用這個抽象方法,該方法用於從輸入流讀取下一個字節,返回一個0到255範圍的值。若是已經到達輸入流結尾處而致使無可讀字節則返回-1,同時,此方法爲阻塞方法,解除阻塞的條件:bash
主要看第三個 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() 方法讀取字節,而且填充到傳入的數組對象中,最後返回讀取的字節數。優化
該方法從輸入流讀取全部剩餘的字節,在此過程是阻塞的,直到全部剩餘字節都被讀取或到達流的結尾或發生異常。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);
}複製代碼
從輸入流中讀取指定長度的字節,並且它能保證必定能讀取到指定的長度,它屬於阻塞方式,用一個 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;
}複製代碼
返回從該輸入流能進行非阻塞讀取的剩餘字節數,當調用 read 讀取的字節數通常會小於該值,有一些InputStream的子實現類會經過該方法返回流的剩餘總字節數,但有些並不會,因此使用時要注意點。.net
這裏抽象類直接返回0,子類中重寫該方法。
public int available() throws IOException {
return 0;
}複製代碼
從輸入流中跳過指定個數字節,返回值爲真正跳過的個數。這裏的實現是簡單經過不斷調用 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;
}複製代碼
此方法用於關閉輸入流,而且釋放相關資源 。
public void close() throws IOException {}複製代碼
從輸入流中按順序讀取所有字節而且寫入到指定的輸出流中,返回值爲轉移的字節數。轉移過程當中可能會發生不肯定次的阻塞,阻塞可能發生在 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;
}複製代碼
是否支持 mark 和 reset 操做,這裏直接返回 false,子類根據實際重寫該方法。
public boolean markSupported() {
return false;
}複製代碼
標記輸入流當前位置,與之對應的是 reset 方法,經過他們之間的組合能實現重複讀取操做。另外它會傳入 readlimit 參數,它用於表示該輸入流中在執行 mark 操做後最多能夠讀 readlimit 個字節後才使 mark 的位置失效。
能夠看到 InputStream 的 mark 方法是什麼都不作的,子類中再具體實現。
public synchronized void mark(int readlimit) {}複製代碼
與 mark 方法對應,它能夠重置輸入流的位置到上次被 mark 操做標識的位置。InputStream 的 reset 方法直接拋出一個 IOException,子類中根據實際狀況實現。
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}複製代碼
如下是廣告和相關閱讀
========廣告時間========
鄙人的新書《Tomcat內核設計剖析》已經在京東銷售了,有須要的朋友能夠到 item.jd.com/12185360.ht… 進行預約。感謝各位朋友。
=========================
相關閱讀:
從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
歡迎關注: