目录
FileInputStream 类简介
FileInputStream 类在 Java 中非常常用,用来读取文件流的。而这种读取方式往往会涉及到流的关闭 close。
如果不关闭 FileInputStream 流会有问题吗?会导致内存泄漏吗?
FileInputStream 的 finalize() 方法
Java 中每个 Object 都有 finalize() 方法,用来在 gc 的时候调用。而 FileInputStream 类中的 finalize() 方法中有一点特别的,它重写了这个方法,并且在里面进行了 close()。
如下代码:
/**
* Ensures that the <code>close</code> method of this file input stream is
* called when there are no more references to it.
*
* @exception IOException if an I/O error occurs.
* @see java.io.FileInputStream#close()
*/
protected void finalize() throws IOException {
if ((fd != null) && (fd != FileDescriptor.in)) {
/* if fd is shared, the references in FileDescriptor
* will ensure that finalizer is only called when
* safe to do so. All references using the fd have
* become unreachable. We can call close()
*/
close();
}
}
可以看到,只要触发了 gc,那么就会调用 finalize() 方法,那么就会自动 close 流。当被 close 之后,也就不会发生内存泄漏了。
那么,不主动关闭,并且不主动触发 System.gc() 的话,它会被 JVM 回收吗?
实际测试
为了更加直观地看到是否调用了 finalize() 方法,这里新建一个 MyFileInputStream 类 extends FileInputStream,为的是重写 FileInputStream 的 finalize() 方法,给里面加入一行打印输出。
代码如下:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* a class extends FileInputStream to test method finalize()
*
* @author sleepybear - https://blog.csdn.net/qq_36670734
* @date 2022/1/9 21:02
*/
public class MyFileInputStream extends FileInputStream {
public MyFileInputStream(File file) throws FileNotFoundException {
super(file);
}
@Override
protected void finalize() throws IOException {
System.out.println("finalize");
super.finalize();
}
}
可以看到,只要执行了 finalize() 方法,那么就会打印一行 “finalize”。
然后新建测试类,如下代码:
import org.apache.commons.codec.digest.DigestUtils;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* 测试 MyFileInputStream 的 Finalize
*
* @author sleepybear - https://blog.csdn.net/qq_36670734
* @date 2022/1/9 21:10
*/
public class TestFinalize {
/**
* 计数
*/
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
listFiles("D://Work//");
System.out.println("遍历结束,等待 2 秒");
TimeUnit.MILLISECONDS.sleep(1000 * 2L);
System.out.println("显式调用 gc");
System.gc();
TimeUnit.MILLISECONDS.sleep(1000 * 2L);
System.out.println("结束");
}
/**
* 递归遍历所有文件,若遍历到 2000 的整数倍,那么输出这个文件的 md5
*
* @param path 路径
*/
public static void listFiles(String path) {
if (path == null || path.length() == 0) {
return;
}
File file = new File(path);
if (!file.exists()) {
return;
}
if (file.isDirectory()) {
// 遇到文件夹,往里面继续递归
File[] files = file.listFiles();
if (files != null && files.length > 0) {
for (File dir : files) {
listFiles(dir.getAbsolutePath());
}
}
}
if (file.isFile()) {
// 遇到是文件,那么计数 +1
count++;
if (count % 2000 == 0) {
// 如果是 2000 的整数倍,那么打印文件的 md5,md5 打印需要用到 commons-codec-1.15.jar
try {
// 这里直接 new MyFileInputStream 并没有显式 close,同时工具方法里面也没有调用 close()
String md5 = DigestUtils.md5Hex(new MyFileInputStream(file));
System.out.println("count = " + count + ", md5 = " + md5);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行代码,得到如下的结果:
结论
上述测试可以看到,即使没有主动 close 流,MyFileInputStream 类(或者是 FileInputStream 类)在结束之后,也会自动被回收。在回收的时候调用 finalize() 方法自行 close 流。
所以在使用 FileInputStream 类,不显式调用 close 方法的话,一般不会造成内存泄漏。
会有其他问题吗
以下结论参考自下述文章
不主动 close 流的话,文件句柄还占用着,如果 JVM 没有及时清理这些流的话,那么可能导致资源泄露问题,可能会因为过多打开文件导致 CPU 和 RAM 占用居高,甚至无法再打开新的文件进行读写。
主动 close 的方式
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
fileInputStream.read();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
fileInputStream.close();
}
}
当 Java 1.7 之后,可以使用 try-with-resources 方式自动 close 流
try (FileInputStream fileInputStream = new FileInputStream(file)) {
fileInputStream.read();
} catch (IOException e) {
e.printStackTrace();
}