在嘗試開啟檔案之前測試檔案的陷阱
有些人建議你在嘗試開啟檔案之前應對檔案應用各種測試,以提供更好的診斷或避免處理異常。例如,此方法嘗試檢查 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
檢查。