42

有人告诉我,使用 Java 的 try-catch 机制会产生一些开销。因此,虽然有必要将抛出已检查异常的方法放在 try 块中以处理可能的异常,但在性能方面最好限制 try 块的大小以仅包含可能抛出异常的那些操作。

我不太确定这是一个明智的结论。

考虑以下两个处理指定文本文件的函数的实现。

即使第一个确实会产生一些不必要的开销,我发现它更容易理解。仅通过查看语句就不太清楚异常究竟来自何处,但评论清楚地表明了哪些语句是负责任的。

第二个比第一个更长更复杂。特别是,第一个漂亮的行阅读习惯必须被破坏以使readLine调用适合 try 块。

在定义中可能引发多个异常的函数中处理异常的最佳实践是什么?

这一个包含 try 块中的所有处理代码:

void processFile(File f)
{
  try
  {
    // construction of FileReader can throw FileNotFoundException
    BufferedReader in = new BufferedReader(new FileReader(f));

    // call of readLine can throw IOException
    String line;
    while ((line = in.readLine()) != null)
    {
      process(line);
    }
  }
  catch (FileNotFoundException ex)
  {
    handle(ex);
  }
  catch (IOException ex)
  {
    handle(ex);
  }
}

这个仅包含在 try 块中引发异常的方法:

void processFile(File f)
{
  FileReader reader;
  try
  {
    reader = new FileReader(f);
  }
  catch (FileNotFoundException ex)
  {
    handle(ex);
    return;
  }

  BufferedReader in = new BufferedReader(reader);

  String line;
  while (true)
  {
    try
    {
      line = in.readLine();
    }
    catch (IOException ex)
    {
      handle(ex);
      break;
    }

    if (line == null)
    {
      break;
    }

    process(line);
  }
}
4

7 回答 7

49

这里的基本前提是错误的:块的大小对try性能没有影响。在运行时实际引发异常会影响性能,这与try块的大小无关。

但是,保持较小的 try 块可以产生更好的程序。

您可能会捕获异常以恢复和继续,或者您可能会捕获它们只是为了将它们报告给调用者(或通过某些 UI 向人类报告)。

在第一种情况下,您可以从中恢复的故障通常非常具体,这会导致更小的try块。

在第二种情况下,一个异常被捕获以便它可以被另一个异常包装并重新抛出,或显示给用户,小块try意味着您可以更准确地知道哪个操作失败,以及哪个操作失败的更高级别的上下文打了那个电话。这允许您创建更具体的错误报告。

当然,这些指南有……例外(对不起!)。例如,在某些情况下,非常具体的错误报告可能是一个安全问题。


了解try块对编译代码的影响可能很有用。它根本不会改变编译的指令!(当然,相应的catch块可以,因为它就像任何其他代码一样。)

块在try与该方法关联的异常表中创建一个条目。该表具有一系列源指令计数器、异常类型和目标指令。当引发异常时,将检查此表以查看是否存在具有匹配类型的条目,以及包含引发异常的指令的范围。如果是,则执行分支到相应的目标编号。

要意识到的重要一点是,除非需要,否则不会参考此表(并且对运行性能没有影响)。(忽略加载类的一点开销。)

于 2010-04-13T23:44:04.180 回答
12

有人告诉我,使用 Java 的 try-catch 机制会产生一些开销。

绝对地。方法调用也有开销。但是您不应该将所有代码放在一种方法中。

不要吹嘘过早的优化喇叭,但重点应该放在易于阅读、组织等上。语言结构很少像系统组织和算法选择那样影响性能。

对我来说,第一个是最容易阅读的。

于 2010-04-13T23:27:40.303 回答
3

不,您应该考虑的唯一一件事是您可以在哪里合理处理异常以及您需要回收哪些资源(使用 finally)。

于 2010-04-13T23:58:37.863 回答
2

这是最糟糕的过早优化。不要这样做。

“我们应该忘记小的效率,比如大约 97% 的时间:过早的优化是万恶之源” - Knuth。

于 2010-04-14T03:01:35.530 回答
1

第二种方法几乎没有什么好处。毕竟,如果您可以成功打开文件但无法从中读取文件,那么您的计算机就出了问题。因此知道 io 异常来自 readLine() 方法很少有用。如您所知,无论如何都会针对不同的问题引发不同的异常(FileNotFoundException等)

只要您使用“逻辑”块确定它的范围,即一次性打开、读取和关闭文件,我会使用第一种方法。它更易于阅读,尤其是在处理 IO 时,try-catch 开销使用的处理器周期将是最小的(如果有的话)。

于 2010-04-13T23:20:47.853 回答
1

在我看来,将 try 块放在可能引发异常的特定代码周围,使其更易于阅读。您可能希望为每个错误显示不同的消息并向用户提供说明,这将根据错误发生的位置而有所不同。

但是,大多数人提到的性能问题与引发异常有关,而不是与 try 块本身有关。

换句话说,只要您从未引发错误,try 块就不会显着影响性能。您不应将 try 块视为另一个流控制结构并引发错误以分支代码。这就是你想要避免的。

于 2010-04-13T23:29:34.430 回答
0

第二种方法将生成一个reader可能尚未初始化的编译器错误。您可以通过将其初始化为 来解决此问题null,但这仅意味着您可以获得 NPE,并且没有任何优势。

于 2010-04-13T23:23:18.043 回答