如何从close()正确处理IOException

时间:2011-01-13 11:13:53

标签: java file-io error-handling ioexception

Java I / O类java.io.Readerjava.io.Writerjava.io.InputStreamjava.io.OutpuStream及其各个子类都有close()方法可以抛出{ {1}}。

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

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

另一方面,在阅读资源时,我完全不清楚IOException可能抛出的原因以及该怎么做。

那么有任何标准推荐吗?

一个相关的问题是Does close ever throw an IOException?,但更多的是关于哪些实现真正抛出,而不是关于如何处理异常。

9 个答案:

答案 0 :(得分:7)

记录下来。

你无法真正做任何事情(例如写一些从错误中恢复过来的代码),但它通常值得让人知道。

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

但实际上,我无法想到任何流的示例,其中读取或写入可以正常工作而不会抛出异常,但结束会。

答案 1 :(得分:4)

  

“忽略或只是记录通常是一个坏主意。”那不是回答这个问题。关于close()的IOException应该怎么办?只是重新抛出它只会进一步推动问题,更难处理。

<强>理论

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

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

您经常可以看到向用户显示的最后3个对话框。实际上,将选择委托给用户是可能的。

我认为重点是不要让系统处于不一致的状态。只是吞下一个接近的异常可能会给你留下一个残缺的文件,以后会导致令人讨厌的错误。

<强>实践

使用已检查的异常有点麻烦。选项出现:

  • 将它们转换为RuntimeException,抛出它们并让它们处于足够高的水平

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

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

    < / LI>

有关IO monad的更多信息

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

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

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

如果需要一个/多个实际例外,可以使用Validation / ValidationNEL代替Option来加强。

答案 2 :(得分:1)

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

是否引发异常

关于要做的事情,您只有3个选择。

  • 捕获并吞下异常,将其丢弃。
  • 让它向上传播。
  • 捕获异常,并引发另一种异常。在这种情况下,您通常会use a chained exception

最后两个选项相似,因为您的代码将引发异常。因此,这个问题实际上是when should my code throw an exception问题的特例。在这种情况下,correct answer to that question是:当且仅当备选方案是不满足您的代码的发布条件 或保持代码的不变 < / em>。职位条件指定您的方法要执行的操作。不变量指定类的特征。

因此,您需要问问自己close()引发异常是否会阻止您的方法执行应做的事情。

  • 如果这不能阻止您的方法执行其工作,则正确的做法是吞下异常。尽管有很多人会告诉你。
  • 如果close()抛出确实阻止了您的方法执行工作,并且该方法可能抛出了IOException,则您将无能为力,just letting the exception propagate upwards
  • 如果close()阻止您的方法执行其工作,但是该方法可能不会抛出IOException,则必须捕获IOException并将其作为另一类异常抛出,并记录{ {1}}是引发异常的原因。

我知道在任何情况下,IOException引发异常都不会阻止计算成功。在您完成读取所需数据后的 中,对InputStream.close()的调用自然会发生。

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

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

是否记录异常

有些人会advise you to log the exception。这几乎总是坏建议。日志消息是程序用户界面的一部分。从根本上讲,您必须始终问自己是否要记录任何内容,因为无用的废语会分散用户的注意力并使阅读日志文件的用户感到困惑(“用户”包括系统管理员)。记录的每个消息都应出于某些有用的目的。每个网站都应提供有助于用户做出决定的信息。

专门报告finally失败的信息很少有用。它如何帮助用户做出决定?如果异常没有阻止您的方法执行其工作,则没有问题,并且用户无需执行任何操作。如果您的程序无法IOException流,并且问题,那么用户可以怎么做?

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

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

答案 3 :(得分:0)

在关闭编写器之前尝试使用flush。关闭异常的主要原因是某些其他资源可能一直在使用数据,或者编写器/读取器可能根本不打开。在关闭之前试图找到资源已经打开的东西。

答案 4 :(得分:0)

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

答案 5 :(得分:0)

这取决于你要关闭的内容。例如,关闭StringWriter不会做任何事情。 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 {
}

对于其他流,请根据需要记录并处理异常。

答案 6 :(得分:0)

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

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

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

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

答案 7 :(得分: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;
}

上面的内容非常详细,但效果很好,因为在资源的I / O期间它会更喜欢{I}期间的IOException,因为它更可能包含开发人员感兴趣的信息。 IOException,因此您可以获得统一的行为。

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

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

答案 8 :(得分:-2)

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

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

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

您可以使用Google代码搜索查看Reader / Writer和Streams的各种实现的一些示例。 BufferedReader和PipedReader都没有实际抛出IOException,所以我认为你并不担心它主要是在保险箱中。如果您真的很担心,可以检查您正在使用的库的实施情况,看看是否需要担心异常。

与其他提到的一样,除了记录IOException之外,你不能做很多事情。

毕竟,try/catch blocks子句中的finally非常难看。

修改

进一步检查会发现IOException的子类,如InterruptedIOExceptionSyncFailedExceptionObjectStreamException,以及从中继承的类。所以只抓一个IOException会过于笼统 - 你不会知道如何处理除了记录之外的信息,因为它可能来自一系列错误。

编辑2:

Urk,BufferedReader是一个糟糕的例子,因为它需要Reader作为输入。我已将其更改为InputStream.java

但是,InputStream&lt; = FilterInputStream&lt; = BufferedInputStream&lt; = InputStreamReader(通过继承和私有实例)的层次结构都是涓涓细流到close()中的InputStream方法。