一、FileInputStream
在FileInputStream中,首先我們需要進行關注的方法,就是read()方法,下面可以來看一下read()方法的源碼:
public int read() throws IOException {
return read0();
}
private native int read0() throws IOException;
在FileInputStream中,對read()方法進行了實現,實現的方式是調用了java的native接口read0,該方法的返回值爲int類型,這個int類型的值,實際上爲將一個 byte 類型的值,轉換爲二進制後,忽略首位的正負值標識後,轉爲的int類型值。如下圖:
/**
* Reads a subarray as a sequence of bytes.
* @param b the data to be written
* @param off the start offset in the data
* @param len the number of bytes that are written
* @exception IOException If an I/O error has occurred.
*/
private native int readBytes(byte b[], int off, int len) throws IOException;
同時,在FileInputStream類中,還提供了另一個native接口,readbytes,這個接口在外層具有兩個read的重載方法,這個方法與read0的底層實現均爲以字節的形式進行讀取,不同之處在於,該方法讀取出的是byte數組,並且會將這些讀取出的數據寫入到傳入的byte數組中,之後,進行返回,並返回所讀取的字節數量。
接下來,來看下一個方法,close()方法,這個方法是一個非常重要的方法:
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
channel.close();
}
fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}
這裏以內部類的方式,傳入了一個Closeable對象,這個對象的實現,則是調用本類的Close方法進行實現。這裏的fb,是一個在構造器中,進行初始化的 FileDescriptor 對象,這個類,是在使用者在不知道文件名稱時進行創建文件纔會使用的一個類,是一個文件的句柄(即文件描述信息),在正常情況下,這個對象是不會對這個類進行使用的。但是我們這裏要再看一下CloseAll()方法的實現方法,以此來了解FileInputStream的具體關閉流程:
synchronized void closeAll(Closeable releaser) throws IOException {
if (!closed) {
closed = true;
IOException ioe = null;
try (Closeable c = releaser) {
if (otherParents != null) {
for (Closeable referent : otherParents) {
try {
referent.close();
} catch(IOException x) {
if (ioe == null) {
ioe = x;
} else {
ioe.addSuppressed(x);
}
}
}
}
} catch(IOException ex) {
/*
* If releaser close() throws IOException
* add other exceptions as suppressed.
*/
if (ioe != null)
ex.addSuppressed(ioe);
ioe = ex;
} finally {
if (ioe != null)
throw ioe;
}
}
}
這裏使用了一個比較巧妙的方法,在try中將原本的 Closeable 對象重新進行了聲明引用,這樣在try,catch中的代碼執行完畢後,便會對調用原本FileInputStream類的native接口close進行關閉這個文件流。同時,關閉FileDescriptor。
二、FileOutPutStream
在看FileOutPutStream的源碼時,如FileInputStream類似,需要先查看write方法,在FileOutPutStream中,一共提供了兩種不同的native方法用於wirte。FileOutputStream中也是依賴於這兩個方法作爲核心的寫入寫出方法進行使用。
/**
* Writes a sub array as a sequence of bytes.
* @param b the data to be written
* @param off the start offset in the data
* @param len the number of bytes that are written
* @param append {@code true} to first advance the position to the
* end of file
* @exception IOException If an I/O error has occurred.
*/
private native void writeBytes(byte b[], int off, int len, boolean append)
throws IOException;
/**
* Writes the specified byte to this file output stream.
*
* @param b the byte to be written.
* @param append {@code true} if the write operation first
* advances the position to the end of file
*/
private native void write(int b, boolean append) throws IOException;
這兩個方法是與read類似的使用方式,分別爲輸出一個byte數組和一個byte字節轉換爲int類型的數字之後的一個二進制值到文件中。
/**
* Closes this file output stream and releases any system resources
* associated with this stream. This file output stream may no longer
* be used for writing bytes.
*
* <p> If this stream has an associated channel then the channel is closed
* as well.
*
* @exception IOException if an I/O error occurs.
*
* @revised 1.4
* @spec JSR-51
*/
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
channel.close();
}
fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}
在FileOutputStream中,與FileInputStram中對於Close方法的處理方式一致,不再過多解釋。
三、總結文件流的flush與關閉
在FileOutputStream中,flush()方法沒有進行重寫,該方法爲空方法,本身不具備任何功能因爲在直接的文件流中,沒有使用緩存進行處理,因此不存在強制性將緩存寫入到文件中的問題。所以,在使用文件流時,使用完成後是不需要進行flush操作的。
關於close()方法的問題,首先我們要看jdk中開發者對於這個方法的備註:
/**
* Closes this file output stream and releases any system resources
* associated with this stream. This file output stream may no longer
* be used for writing bytes.
*
* <p> If this stream has an associated channel then the channel is closed
* as well.
*
* @exception IOException if an I/O error occurs.
*
* @revised 1.4
* @spec JSR-51
*/
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
channel.close();
}
fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}
大致的翻譯後的意思爲:
關閉此文件輸出流並釋放與此流關聯的任何系統資源。此文件輸出流可能不再用於編寫字節。如果該流具有相關信道,則信道也被關閉。
我們在看了翻譯以後就會明白,這個close()方法,本身並不具備釋放內存的功能,而是關閉文件通道。文件通道,則是一個類似於數據庫連接的一種文件連接,在不進行釋放連接的情況下,在某些地方具有對於連接數量的限制,如果不進行及時的釋放,則會導致文件連接過多的異常。同時,由於文件連接沒有被正常的釋放,也可能會導致JVM無法正常的回收文件流對象的內存,最終會導致內存溢出的情況發生。