在尝试打开文件之前测试文件的陷阱
有些人建议你在尝试打开文件之前应对文件应用各种测试,以提供更好的诊断或避免处理异常。例如,此方法尝试检查 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
,但尝试读取文件仍会失败。
第三个问题是测试效率低下。例如,exists
,isFile
和 canRead
调用将各自进行系统调用以执行所需的检查。然后使另一个系统调用打开文件,在后台重复相同的检查。
简而言之,像 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。如果你想为打开的故障生成更好的诊断,可以在处理程序中执行 exists
,isFile
和 canRead
检查。