Java 的輸入輸出老是給人一種很混亂的感受,要想把這個問題搞清楚,必須對各類與輸入輸出相關的類之間的關係有所瞭解。只有你瞭解了他們之間的關係,知道設計這個類的目的是什麼,才能更從容的使用他們。html
咱們先對 Java I/O 的整體結構進行一個總結,再經過分析源代碼,給出把每一個類的關鍵功能是如何實現的。java
Java I/O 的主要結構
Java 的輸入輸出,主要分爲如下幾個部分:git
每一個部分,都包含了輸入和輸出兩部分。github
實現概要
這裏只給出每一個類的實現概要,具體每一個類的實現分析,能夠參見個人 GitHub-SourceLearning-OpenJDK 頁面。根據導航中的連接,進入 java.io ,便可看到對每一個類的分析。數組
字節流輸入
圖1 Java 字節輸入類安全
InputStream 是全部字節輸入類的基類,它有一個未實現的 read
方法,子類須要實現這個 read
方法, 它和數據的來源相關。它的各類不一樣子類,或者是添加了功能,或者指明瞭不一樣的數據來源。markdown
public abstract int read() throws IOException;
ByteArrayInputStream
有一個內部 buffer
, 包含從流中讀取的字節,還有一個內部 counter
, 跟蹤下一個要讀入的字節。多線程
protected byte buf[];
protected int pos;
這個類在初始化時,須要指定一個 byte[]
,做爲數據的來源,它的 read
,就讀入這個 byte[]
中所包含的數據。ide
public ByteArrayInputStream(byte buf[]) {
this.buf = buf;
this.pos = 0;
this.count = buf.length;
}
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
FileInputStream 的數據來源是文件,即從文件中讀取字節。初始化時,須要指定一個文件:函數
public FileInputStream(File file)
throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
fd = new FileDescriptor();
fd.incrementAndGetUseCount();
open(name);
}
之後讀取的數據,都來自於這個文件。這裏的 read
方法是一個 native
方法,它的實現與操做系統相關。
public native int read() throws IOException;
FilterInputStream將其它輸入流做爲數據來源,其子類能夠在它的基礎上,對數據流添加新的功能。咱們常常看到流之間的嵌套,以添加新的功能。就是在這個類的基礎上實現的。因此,它的初始化中,會指定一個字節輸入流:
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
讀取操做,就依靠這個流實現:
public int read() throws IOException {
return in.read();
}
BufferedInputStream 是 FilterInputStream 的子類,因此,須要給它提供一個底層的流,用於讀取,而它自己,則爲此底層流增長功能,即緩衝功能。以減小讀取操做的開銷,提高效率。
protected volatile byte buf[];
內部緩衝區由一個 volatile byte
數組實現,大多線程環境下,一個線程向 volatile
數據類型中寫入的數據,會當即被其它線程看到。
read
操做會先看一下緩衝區裏的數據是否已經所有被讀取了,若是是,就調用底層流,填充緩衝區,再從緩衝區中按要求讀取指定的字節。
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
private byte[] getBufIfOpen() throws IOException {
byte[] buffer = buf;
if (buffer == null)
throw new IOException("Stream closed");
return buffer;
}
DataInputStream
也是 FilterInputStream
的子類,它提供的功能是:能夠從底層的流中讀取基本數據類型,例如 int
, char
等等。DataInputStream
是非線程安全的, 你必須本身保證處理線程安全相關的細節。
例如,readBoolean 會讀入一個字節,而後根據是否爲0,返回 true/false
。
public final boolean readBoolean() throws IOException {
int ch = in.read();
if (ch < 0)
throw new EOFException();
return (ch != 0);
}
readShort 會讀入兩個字節,而後拼接成一個 short 類型的數據。
public final short readShort() throws IOException {
int ch1 = in.read();
int ch2 = in.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (short)((ch1 << 8) + (ch2 << 0));
}
int 和 long 依此類推,分別讀入4個字節,8個字節,而後進行拼接。
可是,浮點數就不能經過簡單的拼接來解決了,而要讀入足夠的字節數,而後再按照 IEEE 754 的標準進行解釋:
public final float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
PushbackInputstream 類也是FilterInputStream的子類,它提供的功能是,能夠將已經讀入的字節,再放回輸入流中,下次讀取時,能夠讀取到這個放回的字節。這在某些情境下是很是有用的。它的實現,就是依靠相似緩衝區的原理。被放回的字節,其實是放在緩衝區裏,讀取時,先查看緩衝區裏有沒有字節,若是有就從這裏讀取,若是沒有,就從底層流裏讀取。
緩衝區是一個字節數組:
讀取時,優先從這裏讀取,讀不到,再從底層流讀取。
public int read() throws IOException {
ensureOpen();
if (pos < buf.length) {
return buf[pos++] & 0xff;
}
return super.read();
}
PipedInputStream 與 PipedOutputStream 配合使用,它們經過 connect
函數相關聯。
public void connect(PipedOutputStream src) throws IOException {
src.connect(this);
}
它們共用一個緩衝區,一個從中讀取,一個從中寫入。
PipedInputStream內部有一個緩衝區,
讀取時,就從這裏讀:
public synchronized int read() throws IOException {
if (!connected) {
throw new IOException("Pipe not connected");
} else if (closedByReader) {
throw new IOException("Pipe closed");
} else if (writeSide != null && !writeSide.isAlive()
&& !closedByWriter && (in < 0)) {
throw new IOException("Write end dead");
}
readSide = Thread.currentThread();
int trials = 2;
while (in < 0) {
if (closedByWriter) {
/* closed by writer, return EOF */
return -1;
}
if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {
throw new IOException("Pipe broken");
}
/* might be a writer waiting */
notifyAll();
try {
wait(1000);
} catch (InterruptedException ex) {
throw new java.io.InterruptedIOException();
}
}
int ret = buffer[out++] & 0xFF;
if (out >= buffer.length) {
out = 0;
}
if (in == out) {
/* now empty */
in = -1;
}
return ret;
}
過程比咱們想的要複雜,由於這涉及兩個線程,須要相互配合,因此,須要檢查不少東西,才能最終從緩衝區中讀到數據。
PipedOutputStream 類寫入時,會調用 PipedInputStream 的receive功能,把數據寫入 PipedInputStream 的緩衝區。
咱們看一下 PipedOutputStream.write
函數:
public void write(int b) throws IOException {
if (sink == null) {
throw new IOException("Pipe not connected");
}
sink.receive(b);
}
能夠看出,調用了相關聯的管道輸入流的 receive
函數。
protected synchronized void receive(int b) throws IOException {
checkStateForReceive();
writeSide = Thread.currentThread();
if (in == out)
awaitSpace();
if (in < 0) {
in = 0;
out = 0;
}
buffer[in++] = (byte)(b & 0xFF);
if (in >= buffer.length) {
in = 0;
}
}
receive
的主要功能,就是把寫入的數據放入緩衝區內。
注意注意的是,這兩個類相互關聯的對象,應該屬於兩個不一樣的線程,不然,容易形成死鎖。
這個系列的第一部分到此結束,擴展閱讀部分的文章很是好,推薦閱讀。
擴展閱讀