【原创】从源码剖析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无法正常的回收文件流对象的内存,最终会导致内存溢出的情况发生。

 

 

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