在嘗試開啟檔案之前測試檔案的陷阱

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