输入/输出流在销毁时是否会关闭?

12

Java中的InputStreams和OutputStreams在销毁时会自动执行close()吗?我完全理解这可能是个糟糕的做法(尤其是在C和C++的世界中),但我很好奇。

另外,假设我有以下代码:

private void foo()
{
    final string file = "bar.txt";
    Properties p = new Properties();
    p.load( new FileInputStream(file) );
    //...
}

在调用p.load()方法后,无命名的FileInputStream是否会超出作用域并因此被销毁,有点像C++的作用域规则吗?我曾尝试在Google上搜索Java的匿名变量作用域,但结果不是我预期的。

谢谢。


请注意,在Java 7或更高版本中使用的“try with resources”将使修复此问题变得相对简单(只需要一个额外的变量赋值和当然是try本身)。还要注意,上面的代码会在我的Eclipse环境中生成警告(关于缺少关闭)。 - Maarten Bodewes
5个回答

19

首先回答:在Java中,"破坏"(在C++中的意义)是不存在的。只有垃圾回收器,当其发现一个对象可以被回收时,它可能会醒来并执行其任务。Java中的垃圾回收器通常不可靠。

第二个回答:有时是,有时不是,但不值得冒险。根据Elliote Rusty Harold的《Java IO》

并非所有流都需要关闭 - 例如,字节数组输出流不需要关闭。然而,与文件和网络连接相关联的流在使用完后应始终关闭。例如,如果您打开一个文件进行写入,并在完成后忘记关闭它,则其他进程可能会被阻止读取或写入该文件。

根据Harold的说法,输入或输出流也是如此。虽然有一些例外情况(他注意到System.in),但一般来说,如果您在完成后不关闭文件流,则会存在风险。并且要在finally块中关闭它们,以确保即使抛出异常也能关闭它们。


6

我曾经以为流会通过垃圾回收自动关闭,但实证证据表明,未手动关闭会导致资源泄漏。你需要像这样做:

try (InputStream in = new FileInputStream("example.txt")) {
    // Do something with the input stream
} catch (IOException e) {
    // Handle the exception
}

InputStream stream = null;

try {
  stream = new FileInputStream("bar.txt");
  Properties p = new Properties();
  p.load(stream);
}
catch(Exception e) {
  // error handling
}
finally {
  closeQuietly(stream);
}

closeQuietly()是Apache的commons-io库中IOUtils类中的一个方法。


5
不,Java中没有析构函数。即使一个特定的引用超出范围(或被修改)后,对象可能仍有其他引用。如果对象不再可达,则流可能在以后的某个时候调用其终结器来关闭流。 Properties.load在关闭传递给它的流方面很奇怪。编辑:Properties.loadFromXML是我大约五年前想到的特殊方法。(API文档应该说是之前而不是之后)。谢谢@tzimnoch。

没有析构函数,但有终结器。java.io中大多数InputStream / OuputStream类的终结器 DO 关闭流。 - ChssPly76
5
@ChssPly76说道:没错,但是关于finalizer何时或者是否被调用根本没有任何保证。毫无保障。 - T.J. Crowder
1
此方法返回后,指定的流仍然保持打开状态。 - tzimnoch

3
变量超出范围并因此被销毁。但是在Java中,变量和变量所指向的对象之间有很大的区别。
当变量超出作用域时,所指向的对象并不会被销毁。只有当Java运行时引擎决定销毁不受任何作用域变量引用的对象时,该对象才会被销毁。

3
简短的回答是“可能,但不要抱有太大希望!”
在实现FileInputStream的类堆栈中,有一个含有finalizer的类,当它被运行时会有效地关闭流(并释放资源)。
问题在于没有保证finalizer会被执行。引用JLS(第12.6节)的话:
“Java编程语言没有规定finalizer何时会被调用,除了说它会在对象的存储空间被重用之前发生。”
这使得流终结变得棘手:
1.如果您的Stream对象永远不成为垃圾,它将永远无法被终结。
2.如果您的Stream对象被老化,那么在它被垃圾收集和终结之前可能需要很长时间。
3.JVM可能需要在识别对象为不可达之后执行额外的GC周期,然后才能执行finalizer。这当然是JLS允许的!
4.从技术上讲,JVM可以合法地永远不执行finalizers,只要它不重新使用具有finalizers的对象的存储空间。(我不知道任何采取这种方式的生产质量JVM,但你永远不知道……)

我认为如果JVM资源不足,它会终止关闭的文件对象。(并不是为了支持不清理自己的行为。) - mike jones
你可能认为...但据我所知,文件描述符用尽并不会触发垃圾回收运行,也不会检测到其他流对象(假设)现在可以被终结。 - Stephen C
@mike jones:不,情况并非如此。这是可以实际证明的。内存不足可能会触发垃圾回收(甚至在抛出“OutOfMemoryError”之前尽最大努力进行),但是垃圾回收与终结 不同。垃圾回收将导致具有终结器的对象被 排队 等待后续终结,但是终结将在何时发生完全未定义。因此,即使耗尽文件描述符(或任何其他非内存资源)也不会触发垃圾回收(正如Stephen已经指出的那样),即使触发了垃圾回收,它也不会强制执行终结。 - Holger

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