题
这个问题在这里已经有答案了:
笔记: 这个问题及其大部分答案可以追溯到 Java 7 发布之前。Java 7 提供 自动资源管理 轻松执行此操作的功能。如果您使用的是 Java 7 或更高版本,您应该前进到 罗斯·约翰逊的回答.
在 Java 中关闭嵌套流的最佳、最全面的方法是什么?例如,考虑设置:
FileOutputStream fos = new FileOutputStream(...)
BufferedOS bos = new BufferedOS(fos);
ObjectOutputStream oos = new ObjectOutputStream(bos);
我知道关闭操作需要得到保证(可能通过使用finally子句)。我想知道的是,是否有必要明确确保嵌套流关闭,或者仅确保关闭外部流(oos)就足够了?
我注意到的一件事,至少在处理这个特定的示例时,内部流似乎只抛出 FileNotFoundExceptions。这似乎意味着,从技术上讲,无需担心如果失败则关闭它们。
这是一位同事写的:
从技术上讲,如果它是正确实施的,那么关闭最外面的流(OO)就足够了。但实施似乎有缺陷。
例子:BufferedOutputStream 继承自 FilterOutputStream 的 close() ,其定义为:
155 public void close() throws IOException {
156 try {
157 flush();
158 } catch (IOException ignored) {
159 }
160 out.close();
161 }
但是,如果出于某种原因出于某种原因引发运行时例外,则永远不会调用。因此,似乎“最安全”(但丑陋)主要担心关闭FOS,这使文件打开。
当您绝对需要确定时,什么被认为是关闭嵌套流的最佳最佳方法?
是否有任何官方 Java/Sun 文档详细介绍了这个问题?
解决方案
我通常会做以下事情。首先,定义一个基于模板方法的类来处理 try/catch 混乱
import java.io.Closeable;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public abstract class AutoFileCloser {
// the core action code that the implementer wants to run
protected abstract void doWork() throws Throwable;
// track a list of closeable thingies to close when finished
private List<Closeable> closeables_ = new LinkedList<Closeable>();
// give the implementer a way to track things to close
// assumes this is called in order for nested closeables,
// inner-most to outer-most
protected final <T extends Closeable> T autoClose(T closeable) {
closeables_.add(0, closeable);
return closeable;
}
public AutoFileCloser() {
// a variable to track a "meaningful" exception, in case
// a close() throws an exception
Throwable pending = null;
try {
doWork(); // do the real work
} catch (Throwable throwable) {
pending = throwable;
} finally {
// close the watched streams
for (Closeable closeable : closeables_) {
if (closeable != null) {
try {
closeable.close();
} catch (Throwable throwable) {
if (pending == null) {
pending = throwable;
}
}
}
}
// if we had a pending exception, rethrow it
// this is necessary b/c the close can throw an
// exception, which would remove the pending
// status of any exception thrown in the try block
if (pending != null) {
if (pending instanceof RuntimeException) {
throw (RuntimeException) pending;
} else {
throw new RuntimeException(pending);
}
}
}
}
}
请注意“待处理”异常——这会处理关闭期间引发的异常会掩盖我们可能真正关心的异常的情况。
finally 首先尝试从任何装饰流的外部关闭,因此如果您有一个包装 FileWriter 的 BufferedWriter,我们会尝试首先关闭 BufferedWriter,如果失败,仍然尝试关闭 FileWriter 本身。(请注意,如果流已经关闭,Closeable 的定义会调用 close() 来忽略该调用)
您可以按如下方式使用上面的类:
try {
// ...
new AutoFileCloser() {
@Override protected void doWork() throws Throwable {
// declare variables for the readers and "watch" them
FileReader fileReader =
autoClose(fileReader = new FileReader("somefile"));
BufferedReader bufferedReader =
autoClose(bufferedReader = new BufferedReader(fileReader));
// ... do something with bufferedReader
// if you need more than one reader or writer
FileWriter fileWriter =
autoClose(fileWriter = new FileWriter("someOtherFile"));
BufferedWriter bufferedWriter =
autoClose(bufferedWriter = new BufferedWriter(fileWriter));
// ... do something with bufferedWriter
}
};
// .. other logic, maybe more AutoFileClosers
} catch (RuntimeException e) {
// report or log the exception
}
使用这种方法,您永远不必担心 try/catch/finally 再次处理关闭文件。
如果这对您的使用来说太重,至少考虑遵循 try/catch 及其使用的“pending”变量方法。
其他提示
关闭链式流时,只需关闭最外层的流即可。任何错误都会沿着链向上传播并被捕获。
参考 Java I/O 流 了解详情。
为了解决这个问题
然而,如果flush()由于某种原因抛出运行时异常,那么out.close()将永远不会被调用。
这是不对的。捕获并忽略该异常后,执行将在 catch 块和 out.close()
语句将被执行。
你的同事提出了一个很好的观点 运行例外。如果您绝对需要关闭流,则始终可以尝试从外向内单独关闭每个流,并在第一个异常处停止。
在Java 7时代, 尝试资源 这当然是可行的方法。正如前面几个答案中提到的,关闭请求从最外层流传播到最内层流。因此,只需一次关闭即可。
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
// do something with ois
}
然而这种模式有一个问题。try-with-resources 不知道内部 FileInputStream,因此如果 ObjectInputStream 构造函数抛出异常,则 FileInputStream 永远不会关闭(直到垃圾收集器到达它)。解决方案是...
try (FileInputStream fis = new FileInputStream(f); ObjectInputStream ois = new ObjectInputStream(fis)) {
// do something with ois
}
这不是那么优雅,但更健壮。这实际上是否是一个问题将取决于在构造外部对象期间可以抛出哪些异常。ObjectInputStream 可以抛出 IOException,应用程序很可能会处理该异常而不会终止。许多流类仅抛出未经检查的异常,这很可能导致应用程序终止。
使用 Apache Commons 处理 IO 相关对象是一个很好的实践。
在里面 finally
子句的使用 IOUtils
IOUtils.closeQuietly(bWriter);IOUtils.closeQuietly(oWritter);
下面的代码片段。
BufferedWriter bWriter = null;
OutputStreamWriter oWritter = null;
try {
oWritter = new OutputStreamWriter( httpConnection.getOutputStream(), "utf-8" );
bWriter = new BufferedWriter( oWritter );
bWriter.write( xml );
}
finally {
IOUtils.closeQuietly(bWriter);
IOUtils.closeQuietly(oWritter);
}
这位同事提出了一个有趣的观点,无论哪种方式都有理由争论。
就我个人而言,我会忽略 RuntimeException
, ,因为未经检查的异常意味着程序中存在错误。如果程序不正确,请修复它。你无法在运行时“处理”一个糟糕的程序。
这是一个令人惊讶的尴尬问题。(即使假设 acquire; try { use; } finally { release; }
代码正确。)
如果装饰器的构造失败,那么您将不会关闭底层流。因此,您确实需要显式关闭底层流,无论是在使用后的finally中,还是在成功将资源移交给装饰器后更困难)。
如果异常导致执行失败,真的要flush吗?
有些装饰器实际上本身就有资源。目前Sun 的实现 ZipInputStream
例如分配了非 Java 堆内存。
据称 (IIRC) Java 库中三分之二的资源使用方式明显是错误的。
同时 BufferedOutputStream
即使在 IOException
从 flush
, BufferedWriter
正确关闭。
我的建议:尽可能直接关闭资源,不要让它们污染其他代码。OTOH,你可能会在这个问题上花费太多时间 - 如果 OutOfMemoryError
表现良好固然很好,但程序的其他方面可能具有更高的优先级,并且库代码在这种情况下可能会被破坏。但我总是这样写:
final FileOutputStream rawOut = new FileOutputStream(file);
try {
OutputStream out = new BufferedOutputStream(rawOut);
... write stuff out ...
out.flush();
} finally {
rawOut.close();
}
(看:没有捕获!)
或许可以使用“执行周围”习惯用法。
Java SE 7 尝试资源 似乎没有提到。它消除了完全显式关闭的需要,我非常喜欢这个想法。
不幸的是,对于 Android 开发来说,这个甜蜜只能通过使用 Android Studio 来实现(我认为)和 定位 Kitkat 及以上版本.
另外,您不必关闭所有嵌套流
检查这个http://ckarthik17.blogspot.com/2011/02/ opening-nested-streams.html
我曾经这样关闭流, 没有在finally中嵌套try-catch 块
public class StreamTest {
public static void main(String[] args) {
FileOutputStream fos = null;
BufferedOutputStream bos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream(new File("..."));
bos = new BufferedOutputStream(fos);
oos = new ObjectOutputStream(bos);
}
catch (Exception e) {
}
finally {
Stream.close(oos,bos,fos);
}
}
}
class Stream {
public static void close(AutoCloseable... array) {
for (AutoCloseable c : array) {
try {c.close();}
catch (IOException e) {}
catch (Exception e) {}
}
}
}
Sun 的 JavaDocs 包括 RuntimeException
s 在他们的文档中,如 InputStream 所示 读取(字节[],int,int) 方法;记录为抛出 NullPointerException 和 IndexOutOfBoundsException。
过滤器输出流 冲洗() 仅记录为抛出 IOException,因此它实际上不会抛出任何异常 RuntimeException
s。任何可以扔掉的东西很可能都会被包裹在一个 IIOException
.
它仍然可能抛出一个 Error
, ,但你对此无能为力;Sun建议您不要尝试捕捉它们。