一、概述
Java IO
庫中的流代表有能力產出資料的資料來源物件或者是有能力接收資料的接收端物件,我們一般把它分成輸入和輸出兩部分:
- 繼承自
InputStream
或Reader
派生的類都含有名為read
的方法,用於讀取單個位元組或位元組陣列。 - 繼承自
OuputStream
或Writer
派生的類都含有名為write
的方法,用於寫入單個位元組或位元組陣列。
我們通常通過疊合多個物件來提供所期望的功能,這其實是一種裝飾器設計模式。
二、位元組輸入流
2.1 InputStream
的作用
它的作用是用來表示那些從不同資料來源產生輸入的類,而最終結果就是通過read
方法獲得資料來源的內容,從資料來源中讀出的內容用int
或byte[]
來表示:
- 位元組陣列
String
物件- 檔案
- 管道
- 一個由其它種類的流組成的序列,以便我們可以將它們收集合併到一個流內
- 其它資料來源,如網路等
2.2 InputStream
原始碼
InputStream
是一個抽象類,所有表示位元組輸入的流都是繼承於它,它實現了以下介面:
public abstract int read() throws IOException
返回輸入流的下一個位元組(next byte
),如果已經到達輸入流的末尾,那麼返回-1
public int read(byte b[]) throws IOException
嘗試從輸入流中讀取b.length
長度的位元組,存入到b
中,如果已經到達末尾返回-1
,否則返回成功寫入到b
中的位元組數。public int read(byte b[], int off, int len) throws IOException
嘗試從輸入流中讀取下len
長度的位元組,如果len
為0
,那麼返回0
,否則返回實際讀入的位元組數,讀入的第一個位元組存放在資料b[off]
中,如果沒有可讀的,那麼返回-1
。public long skip(long n) throws IOException
跳過,並丟棄掉n
個位元組,其最大值為2048
。
2.3 InputStream
的具體實現類
ByteArrayInputStream
它接收byte[]
作為建構函式的引數,我們呼叫read
方法時,就是從byte[]
陣列裡,讀取位元組。
public ByteArrayInputStream(byte buf[], int offset, int length) {
this.buf = buf;
this.pos = offset;
this.count = Math.min(offset + length, buf.length);
this.mark = offset;
}
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
複製程式碼
StringBufferInputStream
已經過時,推薦使用StringReader
。FileInputStream
FileInputStream
支援提供檔名、File
和FileDescription
作為建構函式的引數,它的read
呼叫的是底層的native
方法。
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();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
}
public int read() throws IOException {
return read0();
}
private native int read0() throws IOException;
複製程式碼
PipedInputStream
通過通訊管道來交換資料,如果兩個執行緒希望進行資料的傳輸,那麼它們一個建立管道輸出流,另一個建立管道輸入流,它必須要和一個PipedOutputStream
相連線。
public PipedInputStream(int pipeSize) {
if (pipeSize <= 0) {
throw new IllegalArgumentException("pipe size " + pipeSize + " too small");
}
buffer = new byte[pipeSize];
}
public PipedInputStream(PipedOutputStream out, int pipeSize) throws IOException {
this(pipeSize);
connect(out);
}
@Override
public synchronized int read() throws IOException {
if (!isConnected) {
throw new IOException("Not connected");
}
if (buffer == null) {
throw new IOException("InputStream is closed");
}
lastReader = Thread.currentThread();
try {
int attempts = 3;
while (in == -1) {
// Are we at end of stream?
if (isClosed) {
return -1;
}
if ((attempts-- <= 0) && lastWriter != null && !lastWriter.isAlive()) {
throw new IOException("Pipe broken");
}
notifyAll();
wait(1000);
}
} catch (InterruptedException e) {
IoUtils.throwInterruptedIoException();
}
int result = buffer[out++] & 0xff;
if (out == buffer.length) {
out = 0;
}
if (out == in) {
in = -1;
out = 0;
}
notifyAll();
return result;
}
複製程式碼
SequenceInputStream
將多個InputStream
連線在一起,一個讀完後就完畢,並讀下一個,它接收兩個InputStream
物件或者一個容納InputStream
物件的容器Enumeration
。
public int read() throws IOException {
while (in != null) {
int c = in.read();
if (c != -1) {
return c;
}
nextStream();
}
return -1;
}
複製程式碼
ObjectInputStream
它和一個InputStream
相關聯,源資料都是來自於這個InputStream
,它繼承於InputStream
,並且它和傳入的InputStream
並不是直接關聯的,中間通過了BlockDataInputStream
進行中轉,要關注的就是它的readObject
方法,它會把一個之前序列化過的物件進行反序列化,然後得到一個Object
物件,它的目的在於將(把二進位制流轉換成為物件)和(從某個資料來源中讀出位元組流)這兩個操作獨立開來,讓它們可以隨意地組合。
public ObjectInputStream(InputStream in) throws IOException {
verifySubclass();
bin = new BlockDataInputStream(in);
handles = new HandleTable(10);
vlist = new ValidationList();
serialFilter = ObjectInputFilter.Config.getSerialFilter();
enableOverride = false;
readStreamHeader();
bin.setBlockDataMode(true);
}
public final Object readObject() throws IOException, ClassNotFoundException {
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
複製程式碼
FilterInputStream
它的建構函式引數就是一個InputStream
:
protected FilterInputStream(InputStream in) {
this.in = in;
}
public int read() throws IOException {
return in.read();
}
複製程式碼
這個類很特殊,前面的InputStream
子類都是傳入一個資料來源(Pipe/byte[]/File
)等等,然後通過重寫read
方法從資料來源中讀取資料,而FilterInputStream
則是將InputStream
組合在內部,它呼叫in
去執行InputStream
定義的抽象方法,也就是說它不會改變組合在內部的InputStream
所對應的資料來源。
另外,它還新增了一些方法,這些方法底層還是呼叫了read
方法,但是它封裝了一些別的操作,比如DataInputStream
中的readInt
,它呼叫in
連續讀取了四次,然後拼成一個int
型返回給呼叫者,之所以採用組合,而不是繼承,目的是將(把二進位制流轉換成別的格式)和(從某個資料來源中讀出位元組流)這兩個操作獨立開來,讓它們可以隨意地組合。
public final int readInt() throws IOException {
int ch1 = in.read();
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
}
複製程式碼
三、位元組輸出流
3.1 OuputStream
的作用
OuputStream
決定了輸出要去往的目標:
- 位元組陣列
- 檔案
- 管道
3.2 OutputStream
原始碼
和InputStream
類似,也是一個抽象類,它的子類代表了輸出所要去往的目標,它的關鍵方法如下:
write
方法,前兩個write
方法最終都是呼叫了抽象的write(int oneByte)
方法,最終怎麼寫入是由子類實現的。
3.3 OutputStream
的具體實現類
ByteArrayOutputStream
在ByteArrayOutputStream
的內部,有一個可變長的byte[]
陣列,當我們呼叫write
方法時,就是向這個陣列中寫入資料,它還提供了toByteArray/toString
方法,來獲得當前內部byte[]
陣列中的內容。FileOutputStream
它和上面類似,只不過寫入的終點換成了所開啟的檔案。PipedOutputStream
和PipedInputStream
相關聯。ObjectOutputStream
和ObjectInputStream
類似,只不過它內部組合的是一個OutputStream
,當呼叫writeObject(Object object)
方法時,其實是先將Object
進行反序列化轉換為byte
,再輸出到OuputStream
所指向的目的地。FilterOutputStream
它的思想和FilterInputStream
類似,都是在內部組合了一個OuputStream
,FilterOutputStream
提供了寫入int/long/short
的write
過載函式,當我們呼叫這些函式之後,FilterOutputStream
最終會通過內部的OuputStream
向它所指向的目的地寫入位元組。