19

Java I/O 类java.io.Reader, java.io.Writer,java.io.InputStreamjava.io.OutpuStream它们的各种子类都有一个close()可以抛出IOException.

对于处理此类异常的正确方法是否有任何共识?

我经常看到建议只是默默地忽略它们,但这感觉不对,至少在打开资源进行写入的情况下,关闭文件时出现问题可能意味着无法写入/发送未刷新的数据。

另一方面,在阅读资源时,我完全不清楚为什么close()会抛出以及如何处理它。

那么有什么标准推荐吗?

一个相关的问题是Close 是否会抛出 IOException?,但这更多的是关于哪些实现确实抛出,而不是关于如何处理异常。

4

9 回答 9

7

记录下来。

你真的不能做任何事情(例如编写一些从错误中恢复的代码),但它通常值得让别人知道。

编辑:
在进一步调查和阅读其他评论后,我想说,如果你确实想处理它,那么你将不得不知道实施的细节。相反,您可能需要了解实现的细节来决定是否需要处理它。

但实际上,我想不出任何流的示例,其中读取或写入可以在不引发异常的情况下正常工作,但关闭会。

于 2011-01-13T11:16:18.723 回答
4

“忽略或仅仅记录通常是个坏主意。” 那不是在回答问题。应该如何处理来自 close() 的 IOException?只是重新抛出它只会把问题推得更深,更难处理。

理论

直接回答您的问题:当此 IO 操作失败时,根据情况,您可能希望

  • 回滚更改(不要在磁盘上留下部分写入的文件,删除它)
  • 重试(可能在回滚后)
  • 忽略(并继续)
  • 中断当前任务(取消)

您通常可以在向用户显示的对话框中看到最后 3 个。事实上,将选择委托给用户是一种可能性。

我相信重点是不要让系统处于不一致的状态。仅仅吞下一个关闭的异常可能会给你留下一个残缺的文件,以后会导致严重的错误。

实践

处理检查的异常有点麻烦。选项出现:

  • 将它们转换为RuntimeException,扔掉它们并让它们在足够高的水平上处理

  • 继续使用检查异常并遭受语法痛苦

  • 使用更多可组合的错误处理结构,例如 IO monad(在 Java 中并不真正可用,至少在没有很多大括号的情况下(参见http://apocalisp.wordpress.com/2008/05/16/thrower-functor/)而不是全功率)

更多关于 IO 单子

当您在 IO monad 上下文中返回结果时,您实际上并没有执行 IO 操作,而是返回该操作的描述。为什么?这些动作有一个 API,它们可以很好地组合(相对于 throws/try/catch 屠杀)。

您可以决定是否要检查异常样式处理(使用OptionValidation˙ in the return type), or dynamic style handling, with bracketing only on the finalunsafePerformIO` 调用(实际上执行组合的 IO 操作)。

要了解一些想法,请参阅我的 Scala gist http://gist.github.com/2709535,它承载了该executeAndClose方法。它返回资源处理的结果(如果可用)和未关闭的资源(如果关闭失败)。然后由用户决定如何处理这种不可关闭的资源。

可以用Validation/来加强它,ValidationNEL而不是Option如果一个人也需要一个/多个实际的例外。

于 2012-05-16T11:11:00.653 回答
1

您的代码调用InputStream.close()或者OutputStream.close()是一些高级代码,它们委托给较低级别InputStream​​或OutputStream类以执行一些高级计算。

是否抛出异常

关于做什么,你只有 3 个选择。

  • 捕获并吞下异常,将其丢弃。
  • 让它向上传播。
  • 捕获异常,并抛出不同类型的异常。在这种情况下,您通常会使用链式异常。

最后两个选项相似,因为您的代码会引发异常。所以这个问题实际上是我的代码什么时候应该抛出异常这个问题的一个特例。在这种情况下,该问题的正确答案是:当且仅当替代方案是未能满足代码的后置条件保持代码的不变量。后置条件指定了您的方法的用途。不变量指定类的特征。

因此,您需要问自己,close()抛出异常是否会阻止您的方法执行应该执行的操作。

  • 如果它不会阻止您的方法完成其工作,那么正确的做法是吞下异常。尽管很多人会告诉你。
  • 如果close()throwing 确实阻止了您的方法完成其工作,并且该方法可能 throw IOException,那么您什么也做不了,只是让异常向上传播
  • 如果close()阻止您的方法完成其工作,但该方法可能不会 throw IOException,您必须捕获IOException并重新抛出它作为不同的异常类,将 记录IOException为引发异常的原因。

我不知道在什么情况下InputStream.close()抛出异常会阻止计算成功。在您阅读完您感兴趣的任何数据,该调用close()自然会发生。

但是,输出流可以在内部(在 Java 代码中)或在较低级别(在操作系统中)缓冲要写入输出目的地的数据。OutputStream.close()因此,在成功返回(引发异常)之前,您无法确定对输出流的写入操作实际上是否产生了真正的输出。因此,您应该将抛出的异常视为OutputStream.close()写入失败。

您的方法负责维护其不变量。close()如果抛出异常,有时这将需要清理或回滚操作。即使您希望异常向上传播,您也应该将其代码放在catchorfinally子句中。IOException如果您使用 catch 子句并且想要传播它,则必须重新抛出它。

是否记录异常

有些人会建议您记录异常。这几乎总是不好的建议。日志消息是程序用户界面的一部分。从根本上说,您必须始终问自己是否要记录任何内容,因为无用的措辞会分散阅读您的日志文件的用户的注意力和混淆(“用户”包括系统管理员)。记录的每条消息都应该用于某些有用的目的。每个都应该提供帮助用户做出决定的信息。

专门报告close()失败的情况很少有用。它如何帮助用户做出决定?如果异常没有阻止您的方法完成其工作,则没有问题,用户无需执行任何操作。如果您的程序无法close()流式传输,这一个问题,用户可以做些什么来提供帮助?

低级代码通常根本不负责记录。相反,它执行抽象操作,通过抛出异常向程序的更高级别部分报告失败。关闭流的代码通常是相当低级别的,因此检测到该级别的代码close()太低而无法进行任何日志记录。

失败的具体事实close()很少有用。知道您的方法要执行的抽象操作失败是有用的。这可以通过让更高级别的代码捕获所有预期的异常并报告操作失败来完成,而不是让您的方法准确地报告“关闭失败”。

于 2018-09-08T13:36:35.037 回答
0

在关闭编写器之前尝试使用刷新。关闭异常的主要原因是其他一些资源可能一直在使用数据,或者写入器/读取器可能根本没有打开。尝试在关闭之前找到资源是否打开。

于 2011-01-13T11:22:25.857 回答
0

首先不要忘记将 close() 放在 try catch 块的“finally”部分中。其次,您可以用另一个 try catch 异常包围 close 方法并记录该异常。

于 2011-01-13T11:29:04.627 回答
0

这取决于你要关闭什么。例如,关闭 aStringWriter什么都不做。javadocs对此很清楚。在这种情况下,您可以忽略 IOException,因为您知道它永远不会生成。从技术上讲,您甚至不需要调用close.

/**
 * Closing a <tt>StringWriter</tt> has no effect. The methods in this
 * class can be called after the stream has been closed without generating
 * an <tt>IOException</tt>.
 */
public void close() throws IOException {
}

对于其他流,记录并酌情处理异常。

于 2011-01-13T11:37:11.240 回答
0

通常资源处理应如下所示:

final Resource resource = context.acquire();
try {
    use(resource);
} finally {
    resource.release();
}

在(不太可能)抛出异常的情况下,release抛出的任何异常use都将被丢弃。我对最接近捕手获胜的异常抛出没有问题。我相信 JDK7(?) 中的 ARM 块在这种情况下会做一些疯狂的事情,比如重新抛出use带有异常的release异常。

如果您使用 Execute Around 习惯用法,您可以将这些决定和可能混乱的代码放在一个(或几个)地方。

于 2011-01-13T13:09:07.630 回答
0

忽略或只记录通常是一个坏主意。即使您确实无法从中恢复,但应始终以统一的方式处理 I/O 异常,以确保您的软件以统一的方式运行。

我至少建议如下处理:

//store the first exception caught
IOException ioe = null;
Closeable resource = null;
try{
  resource = initializeResource();
  //perform I/O on resource here
}catch(IOException e){
  ioe = e;
}finally{
  if (resource != null){
    try{
      resource.close();
    }catch(IOException e){
      if(null == ioe){
        //this is the first exception
        ioe = e;
      }else{
        //There was already another exception, just log this one.
        log("There was another IOException during close. Details: ", e);
      }
    }
  }
}

if(null != ioe){
  //re-throw the exception to the caller
  throw ioe;
}

以上内容非常冗长,但效果很好,因为它更喜欢IOException资源上的 I/O 期间而不是关闭期间的 I/O,因为它更有可能包含开发人员感兴趣的信息。当出现问题时,代码也会抛出一个IOException,所以你得到一个统一的行为。

可能有更好的方法,但你明白了。

理想情况下,您将创建一个允许存储兄弟异常的新异常类型,因此如果您获得两个IOExceptions,您可以同时存储它们。

于 2011-01-13T14:21:22.750 回答
-2

好吧,在大多数情况下,close() 实际上不会抛出 IOException。这是InputStream.java的代码:

  public void close() throws IOException
  {
    // Do nothing
  }

关闭网络资源的错误实际上应该是某种类型的错误RuntimeException,因为您可以在程序连接到网络资源后断开网络资源。

您可以使用 Google 代码搜索查看Reader /Writer 和 Streams的各种实现示例。BufferedReader 和 PipedReader 实际上都没有抛出 IOException,所以我认为您基本上不用担心它就可以放心了。如果你真的很担心,你可以检查你正在使用的库的实现,看看你是否需要担心异常。

像其他人提到的那样,除了记录它之外,您对 IOException 无能为力。

毕竟,try/catch blocksfinally从句都相当难看。

编辑:

进一步检查揭示了IOExceptionlike InterruptedIOExceptionSyncFailedException和的子类ObjectStreamException,以及从它继承的类。因此,仅捕获 anIOException就太笼统了——除了记录它之外,您不知道如何处理这些信息,因为它可能来自一系列错误。

编辑2:

UrkBufferedReader是一个不好的例子,因为它需要 aReader作为输入。我已将其更改为InputStream.java

但是,有一个带有InputStream<= FilterInputStream<= BufferedInputStream<=的层次结构InputStreamReader(通过继承和私有实例),所有这些都渗透close()InputStream.

于 2011-01-13T11:35:24.673 回答