Try-Catch 比 Try-With-Resources 更贵还是更便宜

Is Try-Catch More or Less Expensive Than Try-With-Resources

问题

我最近才开始重新使用 Java,但从未有机会使用 try-with-resources。表面上它看起来很棒,因为它可以减少代码,但在幕后,它比传统的 try-catch 更昂贵还是更便宜?我知道 try-catch 已经是一项昂贵的操作,因此我很好奇。

我对这两种类型进行了简单测试,并没有发现太大的区别:

测试示例

尝试资源测试

long startTime = System.currentTimeMillis();
ArrayList<String> list = null;

try (Scanner sc = new Scanner(new File("file.txt"))) {
    list = new ArrayList();
    while (sc.hasNext()) {
        list.add(sc.next());
    }
} catch (Exception ex) {
    System.err.println("Error: " + ex.getMessage());
} finally {
    long endTime = System.currentTimeMillis();
    System.out.println("The program completed in " + (endTime - startTime) + " ms");
}

传统 Try-Catch 测试

long startTime = System.currentTimeMillis();
ArrayList<String> list = null;
Scanner sc = null;

try {
    sc = new Scanner(new File("file.txt"));
    list = new ArrayList();
    while (sc.hasNext()) {
        list.add(sc.next());
    }
} catch (Exception ex) {
    System.err.println("Error: " + ex.getMessage());
} finally {
    sc.close();
    long endTime = System.currentTimeMillis();
    System.out.println("The program completed in " + (endTime - startTime) + " ms");
}

结果

两者都导致了 15-16 毫秒的时间 - 根本没有真正明显的区别。但不可否认,这是一个非常小的测试示例。

我的问题又来了:引擎盖下 try-with-resources 比传统 try-catch 贵还是便宜?

  1. try-catch 不是昂贵的部分。 抛出 异常是(生成堆栈跟踪)。
  2. 上面的
  3. "Expensive"表示"costs some microseconds"。
  4. try-with-resources 只是 try-catch,需要正确的代码才能可靠地关闭资源。
  5. 由于尝试在 HotSpot 等优化运行时中测量性能的所有众所周知的陷阱,您的测量代码无法证明任何事情。你需要热身,重复同样的动作很多次等等。
  6. 如果您的结果超过 10 毫秒,那么显然您不会遇到 try-catch 的问题,这总共会造成几微秒的开销。

Try-catch-finally 和 try-with-resources 具有基本相同的性能,因为在幕后它们生成基本相同的字节码。

但是,您的第二个版本(try..catch..finally)的表述并不完全正确,因为它可能(理论上)在调用 sc.close() 时导致不希望的 NullPointerException。如果构造 Scanner 的行为导致抛出异常,则 sc 将不会被赋值,并且将是 null.

您应该在 try..finally 之外构建 Scanner 并将其更改为:

Scanner sc = null;
try {
    sc = new Scanner(new File("file.txt"));
    ...

至:

Scanner sc = new Scanner(new File("file.txt"));
try {
    ...

或者,您应该在调用 sc.close() 之前检查 finally 子句中的 sc != null。如果您在 try..finally 之外创建扫描器,则没有必要这样做,所以我建议您这样做。

要完成与 try-with-resources 相同的工作,您还需要在 sc.close() 周围放置第二个 try..catch 和一个空的 catch 块,以忽略关闭期间抛出的任何异常.如果你这样做了,我想你就不必太担心 null 检查了。

是苹果和橘子。 ARM(自动资源管理,或 try-with-resources)块比您展示的老式 try-catch-finally 块做的更多。那是因为它生成代码来处理资源关闭中抛出的异常,suppression mechanism. (A related answer 对此进行了一些详细讨论。)

如果您正在编写新代码,请使用 ARM 块。它更易于阅读、维护,而且功能更多。除非您 运行 处于严格受限的环境(如智能卡)中,否则这些优势可能会超过一些额外字节代码的成本。

传统的try-catch:在try块中,你的代码可能会出现异常,这个异常会被抛出,然后被catch块捕获。然后 catch 块将处理异常。如果你在 try 之前打开一个源,比如文件或其他东西,你需要在 finally 中关闭它们,那么 close() 也可能在 finally 中执行抛出一个异常,这将替换 try 中抛出的异常 这种情况在 SE 7 之前通过嵌套的 try-catch 解决,就像这样

try
{
   try
  {
      code with exception;
  }
   finally
 {
      close();
 }

}
catch(Exception e)
{
  Deal the exception
}

这个真的很复杂,所以在SE 7之后我们使用try-with-resource来解决这个问题,当你完成try块时,try(Here)中的资源将全部关闭。

参考 "Core Java" Edition:9 基于 SE 7