关闭Java输入流

81

我对在使用Java InputStream时调用close()方法的用法有些疑问。根据我从大多数开发者那里看到和读到的内容,当不再需要InputStream时,应该始终显式地调用close()。但是,今天我正在研究使用Java属性文件,而我找到的每个示例都有像这样的代码:

Properties props = new Properties();
try {
    props.load(new FileInputStream("message.properties"));
    //omitted.
} catch (Exception ex) {}

在上面的例子中,没有办法显式调用close()方法,因为InputStream在使用后就不可访问了。尽管这似乎与大多数人关于显式关闭的说法相矛盾,但我看到过很多类似使用InputStreams的情况。我阅读了Oracle的JavaDocs,它并没有提到Properties.load()方法是否关闭InputStream。我想知道这是否通常可接受,还是更倾向于像下面这样做:

Properties props = new Properties();
InputStream fis = new FileInputStream("message.properties");
try {
    props.load(fis);
    //omitted.
} catch (Exception ex) {
    //omitted.
} finally {
    try {
        fis.close();
    } catch (IOException ioex) {
        //omitted.
    }
}

哪种方法更好和/或更有效?还是真的无关紧要吗?


1
感谢大家提供的答案。我希望我能接受它们作为“答案”。现在这对我来说有意义了。 - Jason Watkins
8个回答

55

Properties类将输入流封装在LineReader中读取属性文件。 由于您提供了输入流,因此关闭它是您的责任。

第二个示例是处理流的更好方式,不要依赖别人为您关闭它。

您可以进行的一项改进是使用IOUtils.closeQuietly()来关闭流,例如:

Properties props = new Properties();
InputStream fis = new FileInputStream("message.properties");
try {
    props.load(fis);
    //omitted.
} catch (Exception ex) {
    //omitted.
} finally {
    IOUtils.closeQuietly(fis);
}

8
在IOUtils 2.6中,DON'T USE已被弃用并且没有替代品。"请使用try-with-resources语句或手动处理抑制的异常。" - Sergio

53

我会选择使用try-with-resources(至少适用于Java 7+):

Properties props = new Properties();

try(InputStream fis = new FileInputStream("message.properties")) {
    props.load(fis);
    //omitted.
} catch (Exception ex) {
    //omitted.
}

当 try 块退出时,close() 调用应该自动完成。


27

属性教程中的示例在加载后明确关闭了FileInputStream,因此我认为可以安全地假设load方法不负责关闭它,这是您的责任。

// create and load default properties
Properties defaultProps = new Properties();
FileInputStream in = new FileInputStream("defaultProperties");
defaultProps.load(in);
in.close();

仅供参考,我检查了Apache HarmonyProperties实现,并且它在加载时不会关闭流。


“Properties” 教程是我一直期待的解释。感谢提供链接! - Jason Watkins

17

如果您使用Java 7+,则可以使用以下内容:

try(InputStream is = new FileInputStream("message.properties")) {
    // ...
}

14

文档没有提到props.load会关闭输入流。您应该像您建议的那样在finally块中手动关闭输入流。

一个函数关闭一个InputStream不是正常的做法。与非垃圾收集语言中的内存一样,相同的约定也适用于流:如果可能,打开流的人应该关闭流。否则,很容易让流保持打开状态(你认为某个函数会关闭它,但实际上它没有关闭,或者其他原因...)


+1 给“打开流的人应该关闭流”,如果不可能,方法注释应指示调用者这样做。第一个代码示例是不良实践。 - leonbloy
Java 1.6 的最新补丁版本在 props.load(Reader) 方法中添加了以下注释:此方法返回后,指定的流仍然保持打开状态。 在我找到这个问答之前,我已经 TL;DR(太长不看)了 props.load(Reader) 的文档,无法理解为什么我的流仍然打开! - kevinarpe

7
看起来第一个代码示例最终依赖于 FileInputStream 中的 finalize 方法来实际关闭文件。我认为你的第二个示例更好,即使在两种情况下都会关闭文件。
有些情况下,例如字节流,close 什么也不做,可以省略,否则最好在 finally 块中显式关闭文件。打开它,就要关闭它。
Oracle 网站上有一本书叫做 Java 平台性能,附录讨论了 finalizer,它说:
“你几乎总是最好自己清理,而不是依赖 finalizer。使用 finalizer 还可能留下关键资源,这些资源在不确定的时间内不会被回收。如果您正在考虑使用 finalizer 来确保及时释放重要资源,则可能需要重新考虑。”

6

让我对其他人的答案补充一点。

如果你可以导入Apache Commons IO,你就可以利用非常方便的 AutoCloseInputStream 类:包装你的 InputStream ,然后只需使用包装实例即可,在输入结束时或流被显式关闭时,它会自动关闭。


0

因为 FileInputStream 实现了 finalize() 并在 `finalize` 中调用 close()

所以当不经常使用时,没有必要关闭 FileInputStream


3
这是一个糟糕的建议:程序员无法控制finalize()方法何时被调用。它取决于GC、JVM,并且在不同的操作系统或架构上可能会有所不同。如果您依赖于垃圾回收来关闭数百个打开的InputStream对象,而GC没有及时完成,那么就会有数百个打开的流存在。这是finalizer被弃用的原因之一。请参见其他答案中关于try-with-resources的内容。 - Kevin Seymour

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