Java可以识别某些文件和目录,但仍然认为它们不存在

2022-09-03 02:46:24

我在Windows 10上使用Java 17和NTFS。有一个文件显示在Java操作中,但是当我尝试使用Java读取它时,它会抛出一个:A:\foo.barFiles.list()java.nio.file.FileSystemException

A:\foo.bar:该进程无法访问该文件,因为它正被另一个进程使用

没关系。还有另一个低级进程已锁定该文件。当我关闭其他进程时,Java可以很好地访问该文件。事实上,即使是Robocopy,在尝试访问此文件时,也会跳过该文件(实际上,Robocopy似乎复制了该文件,但它没有)。因此,到目前为止,没有什么神秘之处 - 另一个过程是锁定文件以进行独占访问。

但这是奇怪的部分。在大多数情况下,该文件在 Java 中显示为正常:

  • 正如我所提到的,该文件显示在 一个 .A:\foo.barFiles.list()A:\
  • 如果我调用 ,它将按预期返回。Files.isRegularFile(fooBarFile)true
  • 如果我调用 ,它会像我预期的那样转动(在这种情况下很有用)。Files.isReadable(fooBarFile)false
  • 如果我调用,我会看到属性(时间戳等)。Files.readAttributes(fooBarFile, "*")
  • 如果我调用它,则返回 DOS 属性。Files.readAttributes(fooBarFile, DosFileAttributes.class)

但是如果我调用Files.exists(fooBarFile),它会返回false因此,似乎被另一个进程锁定为独占访问的文件将返回 ,对我来说,这似乎不遵循其API中所述的方法的语义。falseexists()exists()

实际上,通过检查是否返回但返回,查看文件是否无法访问似乎很有用;然而,这是出乎意料的,似乎没有记录在案。这是预期的行为吗?它有文档记录吗?它在其他平台上是否工作相同,例如Linux?exists()falseisRegularFile()true

最后,我注意到Files.notExists(fooBarFile)也返回,所以Java并不是说该文件不存在,只是说它不存在。嗯......我能做到这一点的唯一方法是if意味着“可访问”,但API合约不谈论可访问性。该文档添加了:falseexists()exists()notExists()

请注意,此方法不是现有方法的补充。如果无法确定文件是否存在,则两种方法都返回 false。

这似乎也不适用于这种情况。因此,尽管这种行为很有用,但它是出乎意料的,似乎没有记录在案,因此我对过度依赖它犹豫不决。任何人都可以提供更多信息或更好但更权威的文档吗?

更新:类似的事情似乎发生在不可读的目录上。例如,A:\系统卷信息目录在 Windows 上被标记为“隐藏”和“只读”。如果您尝试访问它,它同样会引发异常。但是回来了,即使回来了!实际上,它的行为与上面的项目符号完全相同,只是返回而不是 。java.nio.file.FileSystemExceptionFiles.exists()falseFiles.isDirectory()trueisDirectory()trueisRegularFile()

因此,它似乎是重复的(至少在Windows上的OpenJDK 17上),即使这种行为似乎并不遵循API契约。这是一个JDK错误吗?Files.exists()Files.isReadable()Files.exists()


答案 1

因此,似乎Files.exists()正在复制Files.isReadable()(至少在Windows上的OpenJDK 17上),即使该行为似乎不遵循Files.exists()API协定。这是一个JDK错误吗?

TL;DR

这是它的行为方式,它遵循API协定。我们有2个方法的一个主要原因是,如果它们确定这个问题,这两个方法都会返回。true

  • Files.exist()true 表示文件肯定存在。
  • Files.notExists()true 表示文件绝对不存在。

假并不意味着不然。这也可能意味着系统无法确定结果,如果它不能引发这样的已检查异常,它将返回 false 作为最安全的返回。

  • 如果要执行某些基于文件中不存在的条件的操作则永远不要使用 {...}。在这种情况下,假设您要创建一个新文件,并且要确保另一个文件尚不存在,您应该始终检查If (!Files.exists(file)) If(Files.notExists(file)){...}

  • 另一方面,如果要执行一些基于文件存在于系统中的条件的操作则永远不要使用 {...}。在这种情况下,假设您要读取/修改文件,并且要确保该文件已经存在,您应该始终检查If (!Files.notExists(file)) If(Files.exists(file)){...}

分析解释

我相信 java API 文档可以更好一些,尽管 java API 文档在下面对此更清楚一些:Files.exists(fooBarFile)notExists

请注意,此方法不是现有方法的补充。如果无法确定文件是否存在,则两种方法都返回 false

这是我怀疑正在发生的事情。

根据方法说明

//  @return  
// {@code true} if the file exists; 
// {@code false} if the file does not exist or its existence cannot be determined.
public static boolean exists(Path path, LinkOption... options) {
        if (options.length == 0) {
            FileSystemProvider provider = provider(path);
            if (provider instanceof AbstractFileSystemProvider)
                return ((AbstractFileSystemProvider)provider).exists(path);
        }

        try {
            if (followLinks(options)) {
                provider(path).checkAccess(path); <-------------- if no options provided this is executed
            } else {
                // attempt to read attributes without following links
                readAttributes(path, BasicFileAttributes.class,
                               LinkOption.NOFOLLOW_LINKS);
            }
            // file exists
            return true;
        } catch (IOException x) {
            // does not exist or unable to determine if file exists
            return false;  <----- AccessDeniedException is causing return false
        }

    }

如果有人检查代码,他会看到@return {@code false}... or its existence cannot be determined.

可以从API文档中提到的行中进一步解释:.checkAccess(path)

 * @throws  AccessDeniedException
 *          the requested access would be denied or the access cannot be
 *          determined because the Java virtual machine has insufficient
 *          privileges or other reasons. <i>(optional specific exception)</i>

因此,如果抛出一个也是 的子类(提问者在文件读取期间也从另一个命令获取),那么应该返回,因为该异常不允许JVM确定文件是否确实存在。AccessDeniedExceptionFileSystemExceptionFiles.exists(fooBarFile)false

问题还提到:

文件。notExists(fooBarFile)也返回 false

我认为这也源于同一点。

public static boolean notExists(Path path, LinkOption... options) {
        try {
            if (followLinks(options)) {
                provider(path).checkAccess(path);  <---------- If no options provided this line executes
            } else {
                // attempt to read attributes without following links
                readAttributes(path, BasicFileAttributes.class,
                               LinkOption.NOFOLLOW_LINKS);
            }
            // file exists
            return false;
        } catch (NoSuchFileException x) {
            // file confirmed not to exist
            return true;
        } catch (IOException x) { 
            return false;   <------ In case of AccessDeniedException or some other IOException the return is false.
        }
    }

这验证了这两种方法,并且都设计为在抛出或其他一些方法的情况下返回,因为这将被转换为,JVM无法确定该文件是否存在existsnotExistsfalseAccessDeniedExceptionIOException

因此,考虑到每种方法不能引发这样的异常,每种方法都可以在这种不确定的情况下给出最安全的回报。所以以防万一

  • 如果它无法确定,它就会返回,因此开发人员将不确定该文件是否存在,并继续执行诸如读取文件之类的操作。exists()false
  • 如果它无法确定,它就会返回,因此开发人员不会确定该文件不存在,并继续执行诸如创建新文件之类的操作。notExists()false

因此,只有当它们返回 true 时,这两种方法才应适当地使用并信任为明确的答案。 不表示对 或 的否定回答。而某人永远不应该基于和的结果,至少作为答案的逻辑反转方法。falseexistsnotExists!Files.exists(file)!Files.notExists(file)


答案 2