为什么关闭InputStream是一个好习惯?

48
为什么需要使用close()方法关闭java.io.InputStream及其子类?
对于java.io.OutputStream,例如FileOutputStream,写入文件后如果不使用close()关闭输出流,则我们想要写入文件的数据会留在缓冲区中,而没有被写入文件中。
因此,关闭OutputStream是必要的。但是我从未经历过在不关闭InputStream的情况下遇到任何糟糕的经历。
但所有互联网文章和书籍都建议始终关闭任何类型的Stream,无论是InputStream还是OutputStream
所以我的问题是,为什么需要关闭InputStream?人们说如果你不关闭它,你可能会遇到内存泄漏。那么这种内存泄漏是什么样的呢?

1
资源泄漏在这里可能更令人担忧。处理输入流需要操作系统使用其资源,如果您在使用后不释放它,最终会耗尽资源。 - Andy
在某些JVM中,底层内核会保持FIS打开状态(据我所知),直到你:A)关闭流或B)JVM退出。其他流(特别是处理套接字时)会让其他内容知道你何时完成。例如,当从Socket关闭OutStream时,互联网另一侧的相应输入流也会关闭。 - mailmindlin
@mailmindlin 不,它不会自动关闭流。你必须自己关闭它们。 - user207421
1
你不必为所有的子类都关闭它。例如,java.io.ByteArrayInputStream 不使用任何文件描述符,因此关闭它是一个无操作,参见 https://docs.oracle.com/javase/8/docs/api/java/io/ByteArrayInputStream.html#close-- - Guido Flohr
4个回答

55

InputStream绑定了一个微小的内核资源,即低级文件句柄。此外,只要打开了读取流,该文件就会被锁定一定程度(无法删除、重命名)。假设您不关心被锁定的文件,如果最终需要读取另一个文件并使用新的InputStream打开它,则内核将为您顺序分配一个新的描述符(文件流)。这将最终累加。如果它是一个长时间运行的程序,那么你的程序失败只是时间问题。

处理器的文件描述符表通常具有有限的大小。最终,该进程的文件句柄表将用完可用的插槽。即使对于数千个文件句柄,对于长时间运行的应用程序,也很容易用尽它们,此时您的程序将无法再打开新的文件或套接字。

处理进程的文件描述符表就像某些东西一样简单:

IOHANDLE fds[2048];  // varies based on runtime, IO library, etc.

您开始时有3个占用的插槽(STDIN,STDOUT,STDERR)。此外,任何网络套接字和其他类型的IPC将在同一表中使用一个插槽。填满它,您就对程序执行了拒绝服务攻击。

这些都是需要知道的内容;最好如何应用呢?

如果您依赖于本地对象超出范围,则由垃圾回收器处理,垃圾回收器可以以其自己的方式回收它(非确定性)。不要依赖于GC,请显式关闭流。

在Java中,您需要在实现java.lang.AutoCloseable的类型上使用try-with-resources,"其中包括所有实现java.io.Closeable的对象"根据文档:https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

在C#中,等效的是实现IDisposable的对象上的“using”块


1
如果它是局部变量并且方法返回,那么它是否已关闭?当然,假设我们只使用了Spring的ClassPathResource。 - pinkpanther
1
@pinkpanther - 你需要使用try-with-resources来确保这一点,否则它就由垃圾回收器决定何时收割它们(非确定性)。https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html -- 在C#中也适用,但你需要使用using语句。 - codenheim

10

这并不是 内存 泄漏,而是文件句柄泄漏。操作系统只允许一个进程打开一定数量的文件,如果你没有关闭输入流,则可能会禁止JVM打开更多文件。


我所说的是InputStream的一般情况,而不仅仅是FileInputStream。 - Aditya Singh
1
@Aditya,您最初的示例使用OutputStream并使用FileOutputStream来说明为什么需要关闭它,那么为什么这个示例会更不合适呢? :) - Joachim Isaksson
不是的,因为那只是为了让你理解我的问题。但是这个答案是针对FileInputStream的。而我的问题是关于InputStream的一般性问题。 :) - Aditya Singh
1
所以答案是这样的。大多数流最终在内核中消耗一个文件描述符(FD)。无论它是指文件、套接字还是其他什么,都完全无关紧要。 - user207421
FD代表什么? - Aditya Singh
1
@Aditya 它代表“文件描述符”,但在内核中这是一个非常通用的术语,也用于套接字、通信端口等。 - user207421

5
这是一个潜在的资源泄漏。继承使得无法准确知道哪些资源在使用该方法时可能会泄漏。例如,我可以编写自己的类VoidInputStream,它不分配需要关闭的任何资源。但是,如果您不关闭它,则违反了继承的约定。
请参见http://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html以获取不同输入流的列表。
测试泄漏的资源非常困难。仅次于测试并发问题。不要确定您没有无意中造成一些混乱。

2

一个InputStream可能与许多操作系统资源相关联,例如打开的文件或套接字。close()方法将释放这些资源。

您的程序不需要知道它正在使用哪种类型的InputStream。它只需遵守流在使用后关闭的约定,以便释放任何资源。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接