关于java:不会关闭一个字符串写入器导致泄漏?

Will not closing a stringwriter cause a leak?

我意识到,在Java中,GC将最终清除对象,但我会问,如果不关闭字符串写入器,这是一个错误的做法,目前我正在这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 private static String processTemplate(final Template template, final Map root) {
        StringWriter writer = new StringWriter();
        try {
            template.process(root, writer);
        } catch (TemplateException e) {
            logger.error(e.getMessage());
        } catch (IOException e) {
            logger.error(e.getMessage());
        }
        finally {

        }

        return writer.toString();
    }

我应该关闭编写器并创建这样的新字符串吗:

1
2
3
4
5
6
7
8
String result ="";

...

finally {
  result = writer.toString();
  writer.close();
}

这样做更好吗?


JavaDoc非常明确:

Closing a StringWriter has no effect.

快速查看代码就可以确认:

1
2
public void close() throws IOException {
}


它没有任何非内存资源。它将像其他任何东西一样被垃圾收集。close()可能仅仅存在,因为其他编写器对象确实拥有需要清理的资源,而close()需要满足接口。


不,不关闭StringWriter不会导致泄漏:如前所述,StringWriter#close()是一个nop,而编写器只保存内存,而不保存外部资源,因此在收集编写器时将收集这些资源。(明确地说,它保存对私有字段中不转义对象的对象的引用,具体地说是StringBuffer,因此没有外部引用。)

此外,您通常不应该关闭一个StringWriter,因为它向您的代码添加了样板文件,掩盖了主逻辑,正如我们将看到的那样。不过,为了让读者放心,你是在小心行事,而且是故意这样做的,我建议你评论一下这一事实:

1
2
3
// Don't need to close StringWriter, since no external resource.
Writer writer = new StringWriter();
// Do something with writer.

如果要关闭编写器,最优雅的方法是使用try with resources,当退出try块体时,它将自动调用close()

1
2
3
4
try (Writer writer = new StringWriter()) {
    // Do something with writer.
    return writer.toString();
}

但是,由于writer close()抛出了IOException,因此您的方法现在也需要抛出IOException,即使它从未发生,或者您需要捕获它,以向编译器证明它已被处理。这涉及到:

1
2
3
4
5
6
7
8
9
10
11
Writer writer = new StringWriter();
try {
    // Do something with writer, which may or may not throw IOException.
    return writer.toString();
} finally {
    try {
        writer.close();
    } catch (IOException e) {
        throw new AssertionError("StringWriter#close() should not throw IOException", e);
    }
}

这一级别的样板文件是必要的,因为您不能将catch放在整个try块上,否则您可能会意外地吞下代码体抛出的IOException。即使当前没有,将来也可能会添加一些,编译器会警告您这一点。AssertionError正在记录StringWriter#close()的当前行为,该行为可能在将来的版本中发生变化,尽管这是极不可能的;它也掩盖了在尝试体中可能发生的任何异常(同样,这在实践中不应该发生)。这是太多的样板文件和复杂性,显然您最好省略close()并评论原因。

一个微妙的点是,不仅Writer#close()抛出了IOExceptionStringWriter#close()也抛出了IOException,所以不能通过将变量设置为StringWriter而不是writer来消除异常。这与StringReader不同,它重写close()方法并指定不引发异常!看到我的答案了,我应该关闭一个字符串阅读器吗?。这可能看起来是错误的–为什么您会有一个方法,它只会抛出异常??—但这可能是为了向前兼容,为以后在关闭时抛出一个IOException留出了可能性,因为这通常是作者面临的问题。(这也可能是一个错误。)

总而言之:不关闭一个StringWriter是可以的,但是不做通常正确的事情的原因,即尝试使用资源,仅仅是因为close()声明它抛出了一个异常,实际上它并没有抛出,而处理这个异常则是一个很大的样板。在任何其他情况下,最好只是使用传统的正确的资源管理模式,防止问题和头疼。


在方法的末尾,没有对writer的引用,因此GC将释放它。