一、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无法正常的回收文件流对象的内存,最终会导致内存溢出的情况发生。