0

我知道如何通过 if/else 检查文件是否存在,它在我之前的作业中有效:

File f = new File("file.txt");
    if (f.exists() && f.canRead()) {
        // Code goes here.
    } else { } // Error message.

但是,如果我对缓冲图像执行相同操作,如下所示:

private BufferedImage img;

File f = new File("pic.png");
    if (f.exists() && f.canRead()) {
        img = ImageIO.read(f);
    } else { } // Error message.

然后编译器要求 try/catch 或 @throws 违背了目的。我究竟做错了什么?将我所有的精灵打包在一个 .jar 中并将它们称为资源(我打算在完成后做)会更好吗?

如果这很重要,我在 JRE 1.6 上运行,文件检查在 switch case 语句中。

4

4 回答 4

2

作为一个检查的异常,你必须抓住它。它是否真的被抛出并不重要。您的手动错误处理将无法处理所有情况。考虑您正在从网络驱动器读取,并且在读取时连接可能会中断。

更新查看您上面的评论。如前所述,您必须构建一个 try/catch 块,因为可以抛出异常并处理每个规范所需的已检查异常。通过事先检查,您已经避免了FileNotFoundException. 有时您甚至将我归类为实现根本不会引发任何错误的地方,但由于它在接口中,您仍然必须处理它,例如ByteArryOutputStream.close()

于 2012-11-27T06:39:01.170 回答
2

我们与他进行了一些详细的讨论,他的意思是,如果我确切地知道会发生什么,我应该尝试处理它,而不是诉诸 try/catch 后备。

如果你的导师告诉你应该测试以避免 IO 异常,那么我担心他是完全错误的。

  1. 大多数 I/O 操作都被声明为抛出IOException,这是一个检查异常。当您调用这些方法时,您的代码必须处理或传播异常。你没有选择。Java 语言对所有检查的异常都要求这样做。

  2. 测试所有可能的 IOExceptions 是不切实际的。例如,此代码可能会失败:

      if (f.exists() && f.canRead()) {
          os = new FileInputStream(f);
      }
    

    为什么?因为构造函数可能会抛出 IOException 的原因还有其他,例如硬盘错误、网络错误(如果文件在远程挂载的文件系统上)、强制访问控制失败等。除了尝试打开文件之外,通常无法以任何其他方式检测潜在条件。

  3. 然后是竞争条件的问题。在上面的示例中,在测试文件可读性和尝试打开它之间有一个小的时间窗口。在那个时间窗口内,有可能一些其他线程,或者一些外部进程会删除文件或改变它的访问权限,从而导致打开失败。没有办法关闭那个时间窗口。

然后是为什么你甚至想要避免异常的问题。我怀疑你的老师是“例外否认者”;即,将“例外只用于例外的事情”的建议推向不合逻辑的极端的人之一。

“异常应该只用于特殊的事情”的建议基本上是说你不应该过度使用异常,因为它们有点昂贵。但贵是相对的。

在这个例子中,对File.exists()和对的调用File.canRead()也很昂贵,因为它们都需要一个系统调用,这将花费数千个时钟周期。更糟糕的是,除非你的应用程序有什么不寻常的地方,否则这两个测试将会成功……所以你刚刚做了两个不必要的系统调用来避免无论如何都不会抛出的异常。除非这些测试失败的可能性很高(即 > 50%),否则跳过测试、尝试打开并在发生异常时处理异常会更有效。


我不会给我的教授贴上这样的标签。我们没有专门讨论 I/O 的复杂性,更多的是笼统地说,如果我能避免它,我应该避免它。我不知道(他也没有详细说明)有一些非常不可避免的例外情况。

好的。所以你的教授没有这么说。对他有好处。

是的,它们在不止一种意义上是不可避免的。

至于拒绝使用 .exists() 检查,对性能的影响有那么大吗?

是的。看到这个 -系统调用开销

或者如果你不相信,写一个微基准并测量它。并将其与抛出和捕获异常的开销进行比较。

会是桌面与移动设备之类的问题吗?

没有。或者对于操作系统使用虚拟内存来阻止一个“应用程序”干扰另一个“应用程序”的移动设备来说当然不是。

为故障排除/调试(如果我可以更好地查明问题同时避免程序崩溃)的清晰度进行权衡是否值得?

好吧,我认为这段代码:

try (InputStream is = new FileInputStream(f)) {
    // use file
} catch (IOException ex) {
    System.err.println("IO error: ": ex.getMessage());
}

...比这更简单,更容易维护和调试:

if (!f.exists()) {
    System.err.println("File " + f + " does not exist");
} else if (!f.isDirectory()) {
    System.err.println("File " + f + " is a directory");
} else if (!f.canRead()) {
    System.err.println("File " + f + " is not readable");
} ...
} else {
    try (InputStream is = new FileInputStream(f)) {
        // read
    } catch (IOException ex) {
        System.err.println("IO error: "+ ex.getMessage());
    }
}

你不同意吗?

于 2012-11-27T07:45:04.730 回答
2

使用try/catch 块根本不会破坏目的。

即使该文件存在并且可读,也可以抛出许多其他异常。

可能的例子:

  • 文件在读取时被删除
  • 文件的权限在读取时被撤销
  • 磁盘驱动器在读取时被拔出/卸载

ImageIO.read()方法抛出一个IOException

没有办法避免抓住它或扔掉它。

于 2012-11-27T06:39:58.020 回答
1

如果您看到ImageIO.read方法的声明:-

public static BufferedImage read(File input)
                          throws IOException

它声明IOException要抛出一个,即 a CheckedException。因此,在调用该方法时,您要么需要在try-catch block. 或者,将其声明为在您使用它的方法中抛出:-

File f = new File("pic.png");
if (f.exists() && f.canRead()) {
    try {
        img = ImageIO.read(f);
    } catch (IOException e) {
        e.printStackTrace();
    }
} else { } // Error message.

但是,如果您没有像上述情况那样处理它,那么您必须在方法的throws子句中声明该方法,以便将其传播到caller, 以便它处理它。

通常,任何已检查的异常都应该被处理,或者传播给调用者,调用者逐渐到达main方法,在这种情况下,如果main方法不处理它,它会由 处理JVM,从而导致您的程序终止。

通常,最好在方法本身中处理引发异常的异常,并在需要时将适当的消息返回给调用者。

于 2012-11-27T06:39:35.773 回答