【问题标题】:Can I trust methods in Files will throw NoSuchFileException when expected?我可以相信 Files 中的方法会在预期时抛出 NoSuchFileException 吗?
【发布时间】:2015-03-17 05:34:30
【问题描述】:

java.nio.file.Files API 是对旧的 java.io.File 类的一个非常好的改进,但有一个细节让我觉得很奇怪;除了delete() 之外,没有任何方法记录它们可能会抛出NoSuchFileException,甚至delete() 都表示这是可选的。

我希望能够区分由于丢失文件和其他 IO 问题而导致的故障,但似乎不能保证这是可能的。

如果文件是在两个操作之间创建的,则预先调用 Files.exists() 等的替代方法会冒竞争条件的风险。

我可以期望Files 中的方法会在适当的时候引发NoSuchFileException 吗?如果是这样,这在哪里记录?如果不是,我怎样才能安全地确定失败是由于文件丢失引起的?


示例:在带有 Java 7.0.02 的 Windows 7 上,Files.readAllLines() 方法确实会引发 NoSuchFileException,尽管没有明确记录这样做:

Files.readAllLines(Paths.get("foo"), StandardCharsets.UTF_8)
java.nio.file.NoSuchFileException: foo
  at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79)
  at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
  at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
  at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:229)
  at java.nio.file.Files.newByteChannel(Files.java:315)
  at java.nio.file.Files.newByteChannel(Files.java:361)
  at java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:380)
  at java.nio.file.Files.newInputStream(Files.java:106)
  at java.nio.file.Files.newBufferedReader(Files.java:2660)
  at java.nio.file.Files.readAllLines(Files.java:2993)

【问题讨论】:

    标签: java file-io nio


    【解决方案1】:

    一般来说:不,您不能相信 java.nio.file.Files 中的方法会在预期时抛出 NoSuchFileException,但您可以验证。

    从堆栈跟踪中可以看出,Files 使用FileSystemProvider 来执行文件操作。 FileSystemProvider 实现受到限制(如WindowsFileSystemProvider),进而使用大量本机 (C) 代码。例如,我将NoSuchFileException 追踪到WindowsException,它依赖于操作系统来报告ERROR_FILE_NOT_FOUNDERROR_PATH_NOT_FOUND。另一个例子是newInputStream 路由,它从ChannelInputStreamWindowsChannelFactoryWindowsNativeDispatcher.c,最终调用本机Windows 函数CreateFileW
    考虑到仅针对 Windows 所涉及的代码量,我看不出这对于使用代码 herehere 的 Linux 来说是如何被信任的。

    根据我的经验,Linux (Posix) 文件系统的行为非常一致,但 Windows (NT) 文件系统的行为则不然:我不会假设 Windows 7 的行为与 Windows 8 完全相同。

    最后,关于竞态条件的说明:我知道没有文件系统可以保证目录中列出的文件确实存在(某些文件可能已经被删除,尤其是当使用多个线程对同一目录中的文件进行操作时)。根据我的经验,事先使用像 Files.exists() 这样的方法是个坏主意,除非您要分配大量资源(例如,创建连接以上传文件)。在处理文件时,最好假设一切正常并捕获异常,然后尝试确定哪里出了问题。例如。读取文件时,打开它而不检查文件是否存在,如果发现错误,请检查文件是否存在。这可以节省大量的 I/O 操作,从而提高性能。

    【讨论】:

    • "最好假设一切正常并捕获异常,然后尝试确定问题所在。"我当然同意,为此,如果Files API 针对不同的错误情况显式返回不同的异常,那就太好了。
    • @dimo414 这很好,但有一些奇怪的like this(如果你先删除只读属性,你可以删除一个文件)我不确定它是否可以跨平台保持一致。
    • 同意,有边缘情况,但很糟糕,正常情况没有更清楚地记录。听起来最好的办法是抓住IOException,然后检查Files.exists(),而不必费心寻找NoSuchFileException,即使它有时可能会被抛出。
    • @dimo414 这将使代码更清晰、更易于维护,但我可能会反转它以节省 I/O 操作。假设NoSuchFileException 通常会被抛出(如果被抛出,则假设该文件肯定不存在),但有时可能不会被抛出。使用Boolean 变量fileExistsnull 表示“未知”,使用true/false 表示“肯定不(不)存在”。代码会更难阅读,但如果 I/O 操作成本高昂,那么它可能是值得的。
    【解决方案2】:

    Files 类提供两种删除方法

    delete(Path) 方法删除文件,如果删除失败则抛出异常。例如,如果文件不存在,则会引发 NoSuchFileException。您可以捕获异常以确定删除失败的原因,如下所示:

    try {
        Files.delete(path);
    } catch (NoSuchFileException x) {
        System.err.format("%s: no such" + " file or directory%n", path);
    } catch (DirectoryNotEmptyException x) {
        System.err.format("%s not empty%n", path);
    } catch (IOException x) {
        // File permission problems are caught here.
        System.err.println(x);
    }
    

    deleteIfExists(Path) 方法也会删除文件,但如果文件不存在,则不会抛出异常。当您有多个线程删除文件并且您不想仅仅因为一个线程首先这样做而引发异常时,静默失败非常有用。

    注意: NoSuchFileExceptionDirectoryNotEmptyException 是 Java 7 中引入的新异常。

    【讨论】:

    • 我询问的是 API 的其余部分,而不仅仅是 delete()/deleteIfExists() 方法。 API 的其余部分(例如readAllLines())没有记录它引发了NoSuchFileException,即使它似乎这样做了(至少在 Windows 上)。即使delete() 表示这是一个可选异常,表明调用者不能依赖此行为。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-04
    • 2018-10-30
    • 2017-10-06
    • 1970-01-01
    • 2011-02-19
    • 1970-01-01
    相关资源
    最近更新 更多