在Java中递归删除目录

437

在Java中有没有一种递归删除整个目录的方法?

通常情况下,可以删除空目录。但是当需要删除带有内容的整个目录时,就不那么简单了。

你如何在Java中删除带有内容的整个目录?


4
如果使用非空目录调用File.delete()方法,应该简单地返回false。 - Ben S
如果您正在使用Java 8,请查看@RoK的答案。 - Robin
26个回答

502

你应该查看Apache的commons-io。它有一个FileUtils类可以完成你想要的操作。

FileUtils.deleteDirectory(new File("directory"));

3
这个函数可能是对 Erickson 在他的回答中提供的代码进行包装。 - paweloque
14
它更加全面。在Linux/Unix系统上能够正确处理符号链接等问题。http://svn.apache.org/viewvc/commons/proper/io/trunk/src/java/org/apache/commons/io/FileUtils.java?view=markup - Steve K
1
@Steve K,URL现在为:http://svn.apache.org/viewvc/commons/proper/io/trunk/src/main/java/org/apache/commons/io/FileUtils.java?view=markup#l1569 - Richard EB
2
Java已经内置了一个功能,为什么要添加另一个依赖项?请参见此页面上RoK的答案或https://dev59.com/zlsV5IYBdhLWcg3wpQF8。 - foo
我们可以使用纯Java代码而不是使用依赖项。请查看我的答案。 - pvrforpranavvr
显示剩余2条评论

197

使用Java 7,我们终于可以可靠地检测符号链接(reliable symlink detection)。(我认为Apache的commons-io目前没有可靠的符号链接检测,因为它不能处理使用mklink在Windows上创建的链接。)

出于历史考虑,这里是一个Java 7之前的答案,它遵循符号链接(follows symlinks)

void delete(File f) throws IOException {
  if (f.isDirectory()) {
    for (File c : f.listFiles())
      delete(c);
  }
  if (!f.delete())
    throw new FileNotFoundException("Failed to delete file: " + f);
}

11
File.delete() 没有该功能。 - Ben S
17
@Erickson:FileNotFoundException对于删除失败来说是否不太恰当?如果文件已经不存在,那么它必须已经被删除了,这意味着从语义上讲删除操作并没有失败 - 它根本就没有执行。如果删除因为其他原因失败,那也不是因为文件未被找到。 - Lawrence Dol
46
非常小心。这将取消符号链接。如果你在Linux上有一个名为foo的文件夹和一个链接foo/link,使得link->/,调用delete(new File(foo))将会删除你的文件系统中用户被允许删除的所有内容! - Miquel
5
@Miquel 这没道理 - 为什么我们需要小心呢?提供的代码显然是要删除整个目录,这也是它所做的。我不明白危险在哪里。 - Joehot200
15
@Joehot200,你说得对,对一个目录符号链接调用delete方法不会删除目录本身,只会删除符号链接。如果想要删除目录,就需要使用ReadSymbolicLink显式地跟随符号链接。我的错误!你发现的好。 - Miquel
显示剩余5条评论

167

在Java 7+中,您可以使用Files类。代码非常简单:

Path directory = Paths.get("/tmp");
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
   @Override
   public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
       Files.delete(file);
       return FileVisitResult.CONTINUE;
   }

   @Override
   public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
       Files.delete(dir);
       return FileVisitResult.CONTINUE;
   }
});

2
这个解决方案看起来非常优雅,完全不包含任何目录遍历逻辑! - Zero3
1
“要找到珍珠就要深入海洋。”这是我发现的最好的解决方案。必须深入挖掘才能找到它。太棒了! - Basil Musa
27
在纯Java中,删除一个目录并不是非常简单的事情,但是嘿,我认为这是最好的解决方案。请注意,这里的“Code is”并不是指代码很简单。 - Mat
1
请注意,此处使用的walkFileTree重载 "不会遵循符号链接"。 (Javadoc: http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#walkFileTree(java.nio.file.Path,%20java.nio.file.FileVisitor)) - Stephan
5
在你的postVisitDirectory方法中,应该调用super.postVisitDirectory(dir, exc);来处理如果遍历无法列出目录的情况。请注意不要改变原意,使翻译易于理解。 - Tom Anderson
显示剩余5条评论

88

使用一行代码解决(Java8),递归地删除包括起始目录在内的所有文件和目录:

try (var dirStream = Files.walk(Paths.get("c:/dir_to_delete/"))) {
    dirStream
        .map(Path::toFile)
        .sorted(Comparator.reverseOrder())
        .forEach(File::delete);
}

我们使用比较器来进行反向排序,否则 File::delete 将无法删除可能非空的目录。因此,如果您想保留目录并仅删除文件,请从 sorted() 中删除比较器 完全删除排序并添加文件过滤器:

try (var dirStream = Files.walk(Paths.get("c:/dir_to_delete/"))) {
    dirStream
        .filter(Files::isRegularFile)
        .map(Path::toFile)
        .forEach(File::delete);
}

1
你需要在第一个排序中使用 .sorted(Comparator::reverseOrder) 来删除所有目录。否则,父目录会排在子目录之前,因此不会被删除,因为它不是空的。 对于那些使用Java 8的人来说,这是一个很好的答案! - Robin
3
正确的写法是 .sorted(Comparator.reverseOrder()),建议使用 Comparator::reverseOrder 无效。参见:https://dev59.com/9aDia4cB1Zd3GeqPCVoy#43036643 - user1156544
4
注意在“-o1.compareTo(o2)”中的负号。这等同于使用“.sorted(Comparator.reverseOrder)”排序。 - RoK
4
这个答案存在问题:walk() 返回的流应该被关闭,因为它“包含对一个或多个打开目录的引用”(Javadoc)。 - rolve
1
@rolve 这里没有问题,流将会自动关闭,多亏了“try-with-resources”的帮助,谢谢。 - undefined
显示剩余4条评论

67

Java 7增加了对带有符号链接处理的目录遍历的支持:

import java.nio.file.*;

public static void removeRecursive(Path path) throws IOException
{
    Files.walkFileTree(path, new SimpleFileVisitor<Path>()
    {
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                throws IOException
        {
            Files.delete(file);
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException
        {
            // try to delete the file anyway, even if its attributes
            // could not be read, since delete-only access is
            // theoretically possible
            Files.delete(file);
            return FileVisitResult.CONTINUE;
        }

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

我在这段未经测试的代码中,将此用作从平台特定方法中回退使用:

public static void removeDirectory(Path directory) throws IOException
{
    // does nothing if non-existent
    if (Files.exists(directory))
    {
        try
        {
            // prefer OS-dependent directory removal tool
            if (SystemUtils.IS_OS_WINDOWS)
                Processes.execute("%ComSpec%", "/C", "RD /S /Q \"" + directory + '"');
            else if (SystemUtils.IS_OS_UNIX)
                Processes.execute("/bin/rm", "-rf", directory.toString());
        }
        catch (ProcessExecutionException | InterruptedException e)
        {
            // fallback to internal implementation on error
        }

        if (Files.exists(directory))
            removeRecursive(directory);
    }
}

(SystemUtils来自Apache Commons Lang。Processes是私有的,但它的行为应该很明显。)


我发现Files.walkFileTree存在一个问题——它不足以实现递归删除的版本,即你可以一直删除文件直到没有其他选项为止。它对于快速失败版本来说是足够的,但快速失败并不总是你想要的(例如,如果你正在清理临时文件,你需要立即删除而不是快速失败)。 - Hakanai
我不明白为什么这是正确的。你可以按照自己的方式处理错误——你不必快速失败。我唯一能预见的问题是,它可能无法处理在遍历当前目录期间创建的新文件,但这是一个更适合定制解决方案的特殊情况。 - Trevor Robinson
1
如果你抑制了visitFile的错误并在单个文件上调用walkFileTree,如果失败,你将不会得到任何错误(所以visitFile 必须传播任何发生的错误)。如果你要删除一个目录并且无法删除一个文件,则唯一调用的回调是postVisitDirectory。也就是说,如果访问一个文件时出现错误,则不会访问目录中的其他文件。这就是我的意思。我相信可能有一些方法可以解决这个问题,但是当我们到达这个点时,我们已经编写了比传统递归删除例程更多的代码,因此我们决定不使用它。 - Hakanai
谢谢你提供的第一份代码,它对我很有用。但是我不得不进行修改,因为它无法完全删除一个简单的deltree:我必须忽略“postVisitDirectory”中的异常并返回CONTINUE,因为以下简单树无法完全删除:一个目录内包含另一个目录,其中包含一个文件。所有这些都是在Windows上最简单/正常的情况。 - Dreamspace President
一切都始于我遇到的java.nio.file.DirectoryNotEmptyException。我发现visitFileFailed被使用的情况。如果你的目录结构在Windows中有一个联接类型的链接,这可能会导致两个问题: *) Files.walkFileTree跟随链接进入联接并删除其中的所有内容。 *) 如果联接目标目录已经被删除,则通过Files.walkFileTree解析链接会失败,并在visitFileFailed中捕获NoSuchFileException。 - Andres Luuk
在junction路径上执行大多数IO操作将失败。以下布尔值都为false: Files.exists(file),Files.isDirectory(file),Files.isSymbolicLink(file) 但是,您可以通过以下方式从路径中获取一些信息: BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); boolean isJunction = attrs.isDirectory() && attrs.isOther(); - Andres Luuk

35

我看到我的解决方案基本上与erickson的相同,只是打包为静态方法。将其放在某个位置,它比安装所有Apache Commons要轻得多,因为(如您所见)它非常简单。

public class FileUtils {
    /**
     * By default File#delete fails for non-empty directories, it works like "rm". 
     * We need something a little more brutual - this does the equivalent of "rm -r"
     * @param path Root File Path
     * @return true iff the file and all sub files/directories have been removed
     * @throws FileNotFoundException
     */
    public static boolean deleteRecursive(File path) throws FileNotFoundException{
        if (!path.exists()) throw new FileNotFoundException(path.getAbsolutePath());
        boolean ret = true;
        if (path.isDirectory()){
            for (File f : path.listFiles()){
                ret = ret && deleteRecursive(f);
            }
        }
        return ret && path.delete();
    }
}

21

使用栈而不使用递归方法的解决方案:

File dir = new File("/path/to/dir");
File[] currList;
Stack<File> stack = new Stack<File>();
stack.push(dir);
while (! stack.isEmpty()) {
    if (stack.lastElement().isDirectory()) {
        currList = stack.lastElement().listFiles();
        if (currList.length > 0) {
            for (File curr: currList) {
                stack.push(curr);
            }
        } else {
            stack.pop().delete();
        }
    } else {
        stack.pop().delete();
    }
}

2
+1 使用堆栈。 这将适用于包含深层嵌套子目录的目录,而其他基于堆栈的方法将失败。 - Nathan Osman
5
看到你通常没有问题嵌套几百个方法调用,我认为你很可能会更早地遇到文件系统的限制。 - Bombe
2
对于java.io.File类的list*方法要小心。根据Javadocs的说明:“如果此抽象路径名不表示目录,或者发生I/O错误,则返回null。”所以:if (currList.length > 0) {变成了if (null != currList && currList.length > 0) { - kevinarpe
1
我使用ArrayDeque而不是Stack,因为它略微更快。(非同步) - Wytze

20
如果您使用Spring,则可以使用FileSystemUtils.deleteRecursively方法: FileSystemUtils.deleteRecursively
import org.springframework.util.FileSystemUtils;

boolean success = FileSystemUtils.deleteRecursively(new File("directory"));

17

GuavaGuava 9版本中提供了Files.deleteRecursively(File)方法。

Guava 10开始:

已废弃。该方法存在符号链接检测和竞态条件问题。此功能只能通过调用操作系统命令,例如rm -rfdel /s来适当支持。 此方法计划在Guava 11.0中被移除。

因此,在Guava 11中没有这样的方法。


6
太糟糕了。支付似乎有些粗糙且不便携。如果 Apache Commons 版本正常工作,那么实现起来可能并不困难。 - Andrew McKinlay
6
@andrew,Apache Commons 的实现可能存在类似问题,导致 Guava 移除了他们的实现,请参见 http://code.google.com/p/guava-libraries/issues/detail?id=365。 - orip
Apache Commons版本检测符号链接,并且不会遍历文件的子项。 - Ajax
8
Guava 21.0添加了MoreFiles.deleteRecursively()作为新功能。 - Robert Fleming

12
for(Path p : Files.walk(directoryToDelete).
        sorted((a, b) -> b.compareTo(a)). // reverse; files before dirs
        toArray(Path[]::new))
{
    Files.delete(p);
}

或者如果您想处理 IOException 异常:

Files.walk(directoryToDelete).
    sorted((a, b) -> b.compareTo(a)). // reverse; files before dirs
    forEach(p -> {
        try { Files.delete(p); }
        catch(IOException e) { /* ... */ }
      });

2
这个帮助我创建了一个Scala版本:Files.walk(path)。iterator()。toSeq.reverse.foreach(Files.delete) - James Ward
排序真的必要吗?“walk”方法已经保证了深度优先遍历。 - VGR
比较器可以从 Collections.reverseOrder() 中回收,因此您的代码将是for (Path p : Files.walk(directoryToDelete).sorted(reverseOrder()).toArray(Path[]::new))假设已经静态导入。 - namero999
@namero999 你是指 Comparator.reverseOrder 吗?Files.walk(dir) .sorted(Comparator.reverseOrder()) .toArray(Path[]::new) - Jeff
@Jeff,非常确定你是对的,大部分都是凭记忆完成的 :) - namero999

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接