【原創】從源碼剖析IO流(二)文件流--轉載請註明出處

一、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無法正常的回收文件流對象的內存,最終會導致內存溢出的情況發生。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章