在尝试打开文件之前测试文件的陷阱

有些人建议你在尝试打开文件之前应对文件应用各种测试,以提供更好的诊断或避免处理异常。例如,此方法尝试检查 path 是否对应于可读文件:

public static File getValidatedFile(String path) throws IOException {
    File f = new File(path);
    if (!f.exists()) throw new IOException("Error: not found: " + path);
    if (!f.isFile()) throw new IOException("Error: Is a directory: " + path);
    if (!f.canRead()) throw new IOException("Error: cannot read file: " + path);
    return f;
}

你可以使用上面的方法:

File f = null;
try {
    f = getValidatedFile("somefile");
} catch (IOException ex) {
    System.err.println(ex.getMessage());
    return;
}
try (InputStream is = new FileInputStream(file)) {
    // Read data etc.
}

第一个问题是 FileInputStream(File) 的签名,因为编译器仍然会坚持我们在这里捕获 IOException,或者进一步向上堆栈。

第二个问题是 getValidatedFile 执行的检查并不能保证 FileInputStream 能够成功。

  • 竞争条件:在 getValidatedFile 返回后,另一个线程或单独的进程可以重命名文件,删除文件或删除读取访问权限。这将导致没有自定义消息的普通IOException

  • 这些测试没有涵盖边缘情况。例如,在 SELinux 处于强制模式的系统上,尽管 canRead() 返回 true,但尝试读取文件仍会失败。

第三个问题是测试效率低下。例如,existsisFilecanRead 调用将各自进行系统调用以执行所需的检查。然后使另一个系统调用打开文件,在后台重复相同的检查。

简而言之,像 getValidatedFile 这样的方法是错误的。最好只是尝试打开文件并处理异常:

try (InputStream is = new FileInputStream("somefile")) {
    // Read data etc.
} catch (IOException ex) {
    System.err.println("IO Error processing 'somefile': " + ex.getMessage());
    return;
}

如果你想区分打开和读取时抛出的 IO 错误,可以使用嵌套的 try / catch。如果你想为打开的故障生成更好的诊断,可以在处理程序中执行 existsisFilecanRead 检查。