Java:关闭流后将引用设置为null

6

在关闭流之后将流引用设置为null是一种好的做法吗?这样做会以任何方式释放资源吗?

例如:

BufferedReader input= new BufferedReader(new FileReader("myfile.txt"));

// code

input.close();
input = null;

// possible more code

@Hei:欢迎来到SO。以后如果您编辑问题,可以使用四个空格缩进代码行,这样它们就会显示在特殊的代码块中,使其更易于阅读。 - Pops
谢谢您的评论,我会在未来的问题中注意到这个建议。 - Hei
6个回答

7
不,这是不好的做法。在我看来,您甚至应该考虑将变量设为final
资源处理应按照标准的acquire(); try { use(); } finally { release(); }方式处理。在这种情况下:
final Reader rawIn = new FileReader("myfile.txt"); // Character encoding??
try {
    BufferedReader in = new BufferedReader(rawIn);

    // code

} finally {
    rawIn.close();
}

实际上,这段代码会选择默认的字符编码。我建议明确指定一个硬编码的字符集或从上面参数化。

final InputStream rawIn = new FileInputStream("myfile.txt");
try {
    BufferedReader in = new BufferedReader(
        new InputStreamReader(rawIn, "UTF-8")
    );

    // code

} finally {
    rawIn.close();
}

不要在try块之外(以及资源分配之前)创建包装器流/读取器,因为它们可能会抛出异常。同样,它们的关闭也可能会抛出异常(这实际上是BufferedOutputStream中的一个错误,可能会在flush时抛出)。某些输入流可能有其他资源,因此需要两个try { ... finally { x.close(); }

对于输出,通常应在正常情况下执行flush。但通常在异常情况下不执行。实际上,close通常会执行flush,因此在异常情况下不应将其关闭。如果修饰符既具有flush又具有资源,则必须咬紧牙关。

极少数情况下,清空变量是一个好主意。例如,如果变量是大对象的唯一引用,并且您将创建新的大对象来分配给它,最好清除引用以允许在分配新对象之前回收旧对象。


6
在流声明中使用 final 优秀,加1分。但是关闭内部(rawIn)流则扣1分。应该总是关闭最终的流。例如,在您的情况下,BufferedReader 可能在其内部缓冲区中有一些数据。调用 in 上的 close 将正确地刷新此数据。调用 rawIn 上的 close 将丢弃它。 - Alexander Pogrebnyak
感谢您的回答,并提到了一个空值处理是个好主意的案例。 - Hei
@Alexander是正确的。然而,BufferedReader是一个不好的例子。最好使用BufferedWriter和少于8KB的内容进行测试。 - BalusC
@BalusC 你有调用 flush 吗? - Tom Hawtin - tackline
不,我希望close()来完成它(因此在这个结构中不会发生)。 - BalusC
@BalusC,那么如果装饰构造函数失败,您打算如何关闭实际资源?您真的确定在异常情况下要刷新吗? - Tom Hawtin - tackline

5

不需要。只需要 input.close() 就可以了。记住最好在 finally 块内操作。在调用 close() 之前最好先进行 null 检查,方法如下:

finally{
  if(input!=null){
    input.close();
  }
}

3
如果你在try块之前执行了原始的任务,就不需要进行nullity检查。只有当文件无法打开时才会失败,在这种情况下也没有问题。 - Jon Skeet

3

这可能会使Stream对象本身有资格进行垃圾回收,但是

  1. 在大多数情况下,它很快就会超出作用域
  2. 因此“释放”的内存量非常微不足道

好的,不是立即释放。在后续的收集中,它将经过最终化处理才能释放内存。 - Tom Hawtin - tackline

1
不,不是这样的。close()已经释放了资源。正常的做法如下:
Resource resource = null;
try {
    resource = new Resource();
    // ...
} finally {
    if (resource != null) try { resource.close(); } catch (ResourceException logOrIgnore) {}
}

其中的 Resource 可以是任何外部资源,例如 Java IO API 的 InputStreamOutputStreamReaderWriter,还可以是 JDBC API 的 ConnectionStatementResultSet

在设计良好的代码中,内存不是问题。如果代码离开方法块,它已经可以进行垃圾回收。

您可以将 close() 重构为实用程序方法,例如:

public static void close(Closeable resource) {
    if (resource != null) {
        try {
            resource.close();
        } catch (ResourceException logOrIgnore) {
            // Log it?
        }
    }
}

你可以按照以下方式使用:

} finally {
    close(resource);
}

Apache Commons 提供了许多实用工具方法。


或者您可以将资源获取放置在与 try 相关的正确位置。 - Tom Hawtin - tackline
公正的观点,但有时您希望在同一 try 块中捕获、记录和/或重新抛出异常,如果必要的话,可以作为另一种类型的异常。 - BalusC
1
大胆一点!使用两个try块!!!然后再使用“执行环绕”惯用语。 - Tom Hawtin - tackline
是的,我以前在这里看到过(https://dev59.com/SXRC5IYBdhLWcg3wUfJ2)。谢谢你再次提醒 :) (+1)。 - BalusC

1

除非您手动管理资源池(处理自己的内存),否则无需将输入流置空。理想情况下,您所在的任何函数都很小,并且随着对象超出范围,对该对象的引用也会消失,从而标记它进行垃圾回收。

我提到池化资源,因为如果您不加思考地关闭流而不将对象置空,则可能会意外保留您实际上不需要的对象引用。


0

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