【问题标题】:java.nio.file.Files.delete(Path path) - occasional failure to recursively delete directory using SimpleFileVisitorjava.nio.file.Files.delete(Path path) - 使用 SimpleFileVisitor 递归删除目录偶尔失败
【发布时间】:2013-11-24 23:34:50
【问题描述】:

尝试在从Delete directories recursively in Java 获取的递归删除方法中解决偶尔出现的java.nio.file.DirectoryNotEmptyException

代码(感谢@TrevorRobinson):

static void removeRecursive(Path path) throws IOException {
    Files.walkFileTree(path, new SimpleFileVisitor<Path>() {

        final Logger logger = LoggerFactory.getLogger(this.getClass());
        @Override
        public FileVisitResult visitFile(Path file,
                BasicFileAttributes attrs) throws IOException {
            logger.warn("Deleting " + file.getFileName());
            Files.delete(file);
            logger.warn("DELETED " + file.getFileName());
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) {
            // try to delete the file anyway, even if its attributes could
            // not be read, since delete-only access is theoretically possible
            // I NEVER SEE THIS
            logger.warn("Delete file " + file + " failed", exc);
            try {
                Files.delete(file);
            } catch (IOException e) {
                logger.warn(
                    "Delete file " + file + " failed again", exc);
            }
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc)
                throws IOException {
            if (exc == null) {
                Files.delete(dir);
                return FileVisitResult.CONTINUE;
            }
            // directory iteration failed; propagate exception
            throw exc;
        }
    });
}

致电

try {
    removeRecursive(Paths.get(unzipDirPath));
} catch (IOException e) {
    String msg = "Failed to delete folder " + unzipDirPath;
    if (e instanceof java.nio.file.DirectoryNotEmptyException) {
        msg += ". Still contains : ";
        final File[] listFiles = Paths.get(unzipDirPath).toFile().listFiles();
        if (listFiles != null) for (File file : listFiles) {
            msg += file.getAbsolutePath() + "\n";
        }
    }
    log.error(msg, e);
}

打印(20/40 迭代一次):

22:03:34.190 [http-bio-8080-exec-47] WARN  g.u.d.m.server.servlets.Controller$1 - Deleting batt
22:03:34.192 [http-bio-8080-exec-47] WARN  g.u.d.m.server.servlets.Controller$1 - DELETED batt
22:03:34.192 [http-bio-8080-exec-47] WARN  g.u.d.m.server.servlets.Controller$1 - Deleting wifi
22:03:34.193 [http-bio-8080-exec-47] WARN  g.u.d.m.server.servlets.Controller$1 - DELETED wifi
22:03:34.196 [http-bio-8080-exec-47] ERROR g.u.d.m.s.s.DataCollectionServlet - Failed to delete folder C:\yada\. Still contains : C:\yada\dir\wifi

java.nio.file.DirectoryNotEmptyException: C:\yada\dir
    at sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:265) ~[na:1.7.0_45]
    at sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:103) ~[na:1.7.0_45]
    at java.nio.file.Files.delete(Files.java:1077) ~[na:1.7.0_45]
    at gr.uoa.di.monitoring.server.servlets.Controller$1.postVisitDirectory(Controller.java:128) ~[Controller$1.class:na]
    at gr.uoa.di.monitoring.server.servlets.Controller$1.postVisitDirectory(Controller.java:1) ~[Controller$1.class:na]
    at java.nio.file.FileTreeWalker.walk(FileTreeWalker.java:224) ~[na:1.7.0_45]
    at java.nio.file.FileTreeWalker.walk(FileTreeWalker.java:199) ~[na:1.7.0_45]
    at java.nio.file.FileTreeWalker.walk(FileTreeWalker.java:69) ~[na:1.7.0_45]
    at java.nio.file.Files.walkFileTree(Files.java:2600) ~[na:1.7.0_45]
    at java.nio.file.Files.walkFileTree(Files.java:2633) ~[na:1.7.0_45]
    at gr.uoa.di.monitoring.server.servlets.Controller.removeRecursive(Controller.java:96) ~[Controller.class:na]
    at gr.uoa.di.monitoring.server.servlets.DataCollectionServlet.doPost(DataCollectionServlet.java:153) ~[DataCollectionServlet.class:na]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:641) [servlet-api.jar:na]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:722) [servlet-api.jar:na]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) [catalina.jar:7.0.32]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.32]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) [catalina.jar:7.0.32]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) [catalina.jar:7.0.32]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) [catalina.jar:7.0.32]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168) [catalina.jar:7.0.32]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99) [catalina.jar:7.0.32]
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:929) [catalina.jar:7.0.32]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) [catalina.jar:7.0.32]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407) [catalina.jar:7.0.32]
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1002) [tomcat-coyote.jar:7.0.32]
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:585) [tomcat-coyote.jar:7.0.32]
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310) [tomcat-coyote.jar:7.0.32]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_45]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_45]
    at java.lang.Thread.run(Thread.java:744) [na:1.7.0_45]

请注意,wifi 被报告为已删除 - 更奇怪的是,有时我会收到:

删除文件夹 C:\yada 失败。仍然包含:C:\yada\dir

java.nio.file.DirectoryNotEmptyException: C:\yada\dir

我倾向于得出这样的结论,即有时删除时间太长 - 换句话说,问题是 java.nio.file.Files.delete(Path path) 没有阻塞(因此 C:\yada\dir 在时机成熟时仍然包含文件,有时会被我统计的时间)。那么我该如何解决这个问题呢?

还有java.nio.file.Files.delete(Path path) 需要投掷吗? docs 状态:

在某些操作系统上,当此 Java 虚拟机或其他程序打开并使用该文件时,可能无法删除该文件。

在这种情况下似乎不需要抛出异常。 是否需要java.nio.file.Files.delete(Path path) 才能抛出

【问题讨论】:

标签: file-io java-7 nio java-io


【解决方案1】:

我遇到了同样的问题,结果发现问题是由我要从中删除内容的同一目录的代码中其他位置的未关闭目录文件流引起的。返回的流对象:

Files.list(Path)

必须关闭,因此如果使用该方法,请务必在代码中使用 try-with-resources 构造。

所以我认为删除时间不会太长,我在重新尝试删除目录之前尝试了等待,但没有任何运气。您自己的程序很可能已锁定该资源。结果是尽管它成功返回,但对它的删除调用没有完成(看起来Windows最终会在您自己的程序释放文件后删除该文件)但是当然包含目录不能被删除,因为这确实还没有空。

【讨论】:

  • 我通常对这类东西很小心-“我在重新尝试删除目录之前尝试了等待,但没有任何运气”,您的意思是尽管等待该方法没有失败?在这种情况下,可能是(文件未关闭),虽然我不能确定,因为代码是很久以前的了 - 不过会再看一遍
  • 很傻,Stream 上的终端操作不会在其上调用close()。在我之前的 Files.list(path) 上使用 try-with-resources 为我解决了同样的问题。
  • 谢谢,这很容易被忽略。经过 30 分钟的调试,原来是这个原因。
【解决方案2】:

我知道这是一个非常古老的线程 - 但我遇到了同样的问题,我花了很长时间才修复它。 我认为这种不当行为是由时间问题引起的(看起来它只发生在 Windows 上),所以我暂停了 postVisitDirectory 方法。这很有效,这就是我最终想出的:

在不抛出 DirectoryNotEmptyException 的情况下执行删除的方法:

private boolean isDeleted(Path dir) throws IOException {
    boolean deleted = false;
    try {
        Files.delete(dir);
        deleted = true;
    } catch (DirectoryNotEmptyException e) {
    // happens sometimes if Windows is too slow to remove children of a directory
    deleted = false;
    }
    return deleted;
}

及其在循环中的使用:

public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
    if (e == null) {
        int maxTries = 5;
        int count = 0;
        boolean deleted = false;
        do {
            if ((deleted = this.isDeleted(dir))) {
                break;
            } else {
                // wait a bit and try again
                count++;
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e1) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        } while (count < maxTries);
        // gone?
        if (!deleted) {
            throw new DirectoryNotEmptyException(dir.toString());
        }
        // go ahead
        return FileVisitResult.CONTINUE;
    }
    throw e;
}

安迪

【讨论】:

    【解决方案3】:

    我会评论 @user3485962 的答案,但没有足够的分数!

    下面是一个使用 try 的示例,在创建列表后关闭流:

        /**  
         * @param dir The directory to list.
         * @return A list of files and directories in dir.
         * @throws IOException If encountered.
         */
        public static List<Path> getList(Path dir) throws IOException {
            try (Stream<Path> s = Files.list(dir)) {
                return s.collect(Collectors.toList());
            }
        }
    

    【讨论】:

    【解决方案4】:

    您可以将当前正在迭代的文件的名称临时存储在一个变量中,并对DirectoryNotEmptyException 执行try-catch。发生此异常时,捕获它并抛出您自己的指定文件的异常。

    try {
     Files.delete(file);
    } catch (DirectoryNotEmptyException  e) {
     throw new MySpecificException(file.getFileName());
    }
    
    class MySpecificException extends Exception {
     public MySpecificException() { }
    
     public MySpecificException(string filename) {
      super(filename);
     }  
    }
    

    可以通过e.getMessage();获取文件名

    我假设Files.delete() 在遇到无法删除的文件时会继续删除目录中的文件。如果是这种情况,我的方法仍然有效:只需返回目录中所有文件的列表,而不是目录的文件名。它应该只包含无法删除的文件,并且您仍然有您的解决方案。

    【讨论】:

    • 离题 - 您正在捕获的 file 是非空目录 - 递归方法应删除目录中 的所有文件,但这是失败的 - 并且我想看看哪个文件没有被删除,为什么!
    • 我想知道 why 正如问题中明确指出的那样 - 我已经有一个列表 - 请参阅我的编辑。天哪,我什至不确定删除 不会抛出 - 文档是 unclear
    • 您可能希望对试图帮助您的人更有礼貌。抛出 DirectoryNotEmptyException 是因为您试图删除其中包含文件的文件夹。这是以一种简单的方式进行编程的,它只是跳过它无法删除的文件:不引发任何事件,也不抛出异常。如果要检查原因,可以使用File.canRead()file.CanWrite()File.canExecute() 来查看是否是权限问题和/或是否正在使用中。这些应该是无法删除文件的唯一原因。
    • 请注意您没有回答问题 - 我觉得不仔细阅读问题也不礼貌(您可能希望按照修订来查看例如我说我可以列出自编辑 0 以来的文件:stackoverflow.com/revisions/19935624/1- 你过来回复我说我可以列出文件,完全没有抓住重点!)。我到底哪里粗鲁了?天哪,去了(不清楚的)文档。无论如何 - 回到问题:“这是以一种方式编程的,它只是跳过它无法删除的文件” - 这是 this
    【解决方案5】:

    我上面评论的扩展。

    当 Java 在做某事时遇到问题时,它会抛出 Exception。与所有其他类型一样,异常可以继承自。 API 将指定每个方法抛出的已检查异常。 java.iojava.nio 中的方法通常会抛出 IOException 或其中一个孩子。如果您想创建一个方法来告诉您为什么文件操作(在这种情况下为删除)失败,您可以执行以下操作:

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
           //You don't need to try to delete the file again since this method was called
           //because the deletion failed. Instead...
           if (exc instanceof NoSuchFileException) {
                System.out.println("Could not find the file: " + file);
           } else if (exc instanceof DirectoryNotEmptyException) {
                System.out.println("The directory [+ " file + "] was not empty.");
           } else {
                System.out.println("Could not delete file [" + file
                    + "]. There was a problem communicating with the file system");
           }
    
           return FileVisitResult.CONTINUE;
    }
    

    您可以根据需要更改实际的程序响应,但这应该会给您大致的想法。

    【讨论】:

    • “由于调用了此方法,因此您无需再次尝试删除文件”->错误检查我的答案中的链接。我也已经这样做了 - 与我的实际问题无关 - 这是:如果失败则删除抛出?文档不清楚
    • 出于好奇,你了解returns和throws的区别吗?
    猜你喜欢
    • 2020-01-21
    • 2010-09-24
    • 1970-01-01
    • 1970-01-01
    • 2010-10-14
    • 2015-06-18
    • 1970-01-01
    • 1970-01-01
    • 2014-05-12
    相关资源
    最近更新 更多