在Java中递归地删除目录

在Java中是否有一种方法可以递归地删除整个目录?

在正常情况下,可以删除一个空目录。 然而,当涉及到删除包含内容的整个目录时,就不再那么简单了

如何在Java中删除包含内容的整个目录?

280968 次浏览

在Java 7中,我们终于可以通过可靠的符号链接检测来做到这一点。(我不认为Apache的commons-io现在有可靠的符号链接检测,因为它不处理用mklink创建的Windows上的链接)。

出于历史原因,这里有一个java 7之前的答案,遵循符号链接。

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);
}

也许这个问题的解决方案是使用erickson回答中的代码重新实现File类的delete方法:

public class MyFile extends File {


... <- copy constructor


public boolean delete() {
if (f.isDirectory()) {
for (File c : f.listFiles()) {
return new MyFile(c).delete();
}
} else {
return f.delete();
}
}
}

你应该看看Apache的commons-io。它有一个FileUtils类,可以做你想做的事情。

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

这是一个接受命令行参数的基本main方法,您可能需要附加自己的错误检查或将其塑造为您认为合适的方式。

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;


public class DeleteFiles {


/**
* @param intitial arguments take in a source to read from and a
* destination to read to
*/
public static void main(String[] args)
throws FileNotFoundException,IOException {
File src = new File(args[0]);
if (!src.exists() ) {
System.out.println("FAILURE!");
}else{
// Gathers files in directory
File[] a = src.listFiles();
for (int i = 0; i < a.length; i++) {
//Sends files to recursive deletion method
fileDelete(a[i]);
}
// Deletes original source folder
src.delete();
System.out.println("Success!");
}
}


/**
* @param srcFile Source file to examine
* @throws FileNotFoundException if File not found
* @throws IOException if File not found
*/
private static void fileDelete(File srcFile)
throws FileNotFoundException, IOException {
// Checks if file is a directory
if (srcFile.isDirectory()) {
//Gathers files in directory
File[] b = srcFile.listFiles();
for (int i = 0; i < b.length; i++) {
//Recursively deletes all files and sub-directories
fileDelete(b[i]);
}
// Deletes original sub-directory file
srcFile.delete();
} else {
srcFile.delete();
}
}
}

我希望这对你有所帮助!

番石榴番石榴9之前一直支持Files.deleteRecursively(File)

番石榴10:

此方法受符号链接检测差和竞态条件的影响。这个功能只能通过使用操作系统命令来支持,比如rm -rfdel /s这个方法计划在Guava 11.0版本中从Guava中移除。

因此,在番石榴11中没有这样的方法。

刚刚看到我的解决方案或多或少与埃里克森的相同,只是包装成一个静态方法。把它放到某个地方,它的重量比安装所有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();
}
}

使用符号链接和上面的代码失败的两种方式…也不知道怎么解决。

方法# 1

运行这个来创建一个测试:

echo test > testfile
mkdir dirtodelete
ln -s badlink dirtodelete/badlinktodelete

下面你可以看到你的测试文件和测试目录:

$ ls testfile dirtodelete
testfile


dirtodelete:
linktodelete

然后运行common -io deleteDirectory()。它崩溃,说文件没有找到。不知道其他例子是怎么做的。Linux的rm命令会简单地删除链接,目录上的rm -r也会删除链接。

Exception in thread "main" java.io.FileNotFoundException: File does not exist: /tmp/dirtodelete/linktodelete

Way #2

Run this to create a test:

mkdir testdir
echo test > testdir/testfile
mkdir dirtodelete
ln -s ../testdir dirtodelete/dirlinktodelete

下面你可以看到你的测试文件和测试目录:

$ ls dirtodelete testdir
dirtodelete:
dirlinktodelete


testdir:
testfile

然后运行common -io deleteDirectory()或人们发布的示例代码。它不仅会删除目录,还会删除位于被删除目录之外的testfile。(它隐式地取消对目录的引用,并删除内容)。Rm -r只会删除链接。你需要使用类似这样的方法删除被解引用的文件:"find -L dirtodelete -type f -exec rm {} \;"

$ ls dirtodelete testdir
ls: cannot access dirtodelete: No such file or directory
testdir:
public void deleteRecursive(File path){
File[] c = path.listFiles();
System.out.println("Cleaning out folder:" + path.toString());
for (File file : c){
if (file.isDirectory()){
System.out.println("Deleting file:" + file.toString());
deleteRecursive(file);
file.delete();
} else {
file.delete();
}
}
path.delete();
}
public static void deleteDirectory(File path)
{
if (path == null)
return;
if (path.exists())
{
for(File f : path.listFiles())
{
if(f.isDirectory())
{
deleteDirectory(f);
f.delete();
}
else
{
f.delete();
}
}
path.delete();
}
}

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是私有的,但是它的行为应该是明显的。)

一个有堆栈且没有递归方法的解决方案:

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();
}
}

你可以用:

org.apache.commons.io.FileUtils.deleteQuietly(destFile);

删除文件,从不抛出异常。如果file是目录,请删除该目录及其所有子目录。 File.delete()和这个方法的区别是: 待删除的目录不需要为空目录。 当无法删除文件或目录时,不会抛出异常。< / em > < / p >

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) { /* ... */ }
});

在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;
}
});

如果你有Spring,你可以使用FileSystemUtils.deleteRecursively:

import org.springframework.util.FileSystemUtils;


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

虽然可以使用file.delete()轻松删除文件,但要删除的目录必须为空。使用递归可以很容易地做到这一点。例如:

public static void clearFolders(String[] args) {
for(String st : args){
File folder = new File(st);
if (folder.isDirectory()) {
File[] files = folder.listFiles();
if(files!=null) {
for(File f: files) {
if (f.isDirectory()){
clearFolders(new String[]{f.getAbsolutePath()});
f.delete();
} else {
f.delete();
}
}
}
}
}
}

没有公共IO和<Java SE 7

public static void deleteRecursive(File path){
path.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.isDirectory()) {
pathname.listFiles(this);
pathname.delete();
} else {
pathname.delete();
}
return false;
}
});
path.delete();
}

在遗留项目中,我需要创建本地Java代码。我创建了类似于Paulitex代码的代码。看到:

public class FileHelper {


public static boolean delete(File fileOrFolder) {
boolean result = true;
if(fileOrFolder.isDirectory()) {
for (File file : fileOrFolder.listFiles()) {
result = result && delete(file);
}
}
result = result && fileOrFolder.delete();
return result;
}
}

单元测试:

public class FileHelperTest {


@Before
public void setup() throws IOException {
new File("FOLDER_TO_DELETE/SUBFOLDER").mkdirs();
new File("FOLDER_TO_DELETE/SUBFOLDER_TWO").mkdirs();
new File("FOLDER_TO_DELETE/SUBFOLDER_TWO/TEST_FILE.txt").createNewFile();
}


@Test
public void deleteFolderWithFiles() {
File folderToDelete = new File("FOLDER_TO_DELETE");
Assert.assertTrue(FileHelper.delete(folderToDelete));
Assert.assertFalse(new File("FOLDER_TO_DELETE").exists());
}


}

一行程序解决方案(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);
}

为了更安全的使用,我编写了有3个安全标准的程序。

package ch.ethz.idsc.queuey.util;


import java.io.File;
import java.io.IOException;


/** recursive file/directory deletion
*
* safety from erroneous use is enhanced by three criteria
* 1) checking the depth of the directory tree T to be deleted
* against a permitted upper bound "max_depth"
* 2) checking the number of files to be deleted #F
* against a permitted upper bound "max_count"
* 3) if deletion of a file or directory fails, the process aborts */
public final class FileDelete {
/** Example: The command
* FileDelete.of(new File("/user/name/myapp/recordings/log20171024"), 2, 1000);
* deletes given directory with sub directories of depth of at most 2,
* and max number of total files less than 1000. No files are deleted
* if directory tree exceeds 2, or total of files exceed 1000.
*
* abort criteria are described at top of class
*
* @param file
* @param max_depth
* @param max_count
* @return
* @throws Exception if criteria are not met */
public static FileDelete of(File file, int max_depth, int max_count) throws IOException {
return new FileDelete(file, max_depth, max_count);
}


// ---
private final File root;
private final int max_depth;
private int removed = 0;


/** @param root file or a directory. If root is a file, the file will be deleted.
*            If root is a directory, the directory tree will be deleted.
* @param max_depth of directory visitor
* @param max_count of files to delete
* @throws IOException */
private FileDelete(final File root, final int max_depth, final int max_count) throws IOException {
this.root = root;
this.max_depth = max_depth;
// ---
final int count = visitRecursively(root, 0, false);
if (count <= max_count) // abort criteria 2)
visitRecursively(root, 0, true);
else
throw new IOException("more files to be deleted than allowed (" + max_count + "<=" + count + ") in " + root);
}


private int visitRecursively(final File file, final int depth, final boolean delete) throws IOException {
if (max_depth < depth) // enforce depth limit, abort criteria 1)
throw new IOException("directory tree exceeds permitted depth");
// ---
int count = 0;
if (file.isDirectory()) // if file is a directory, recur
for (File entry : file.listFiles())
count += visitRecursively(entry, depth + 1, delete);
++count; // count file as visited
if (delete) {
final boolean deleted = file.delete();
if (!deleted) // abort criteria 3)
throw new IOException("cannot delete " + file.getAbsolutePath());
++removed;
}
return count;
}


public int deletedCount() {
return removed;
}


public void printNotification() {
int count = deletedCount();
if (0 < count)
System.out.println("deleted " + count + " file(s) in " + root);
}
}

处理异常的最佳解决方案是,方法抛出的异常应该总是描述该方法试图(和失败)做什么:

private void deleteRecursive(File f) throws Exception {
try {
if (f.isDirectory()) {
for (File c : f.listFiles()) {
deleteRecursive(c);
}
}
if (!f.delete()) {
throw new Exception("Delete command returned false for file: " + f);
}
}
catch (Exception e) {
throw new Exception("Failed to delete the folder: " + f, e);
}
}

下面的代码递归删除给定文件夹中的所有内容。

boolean deleteDirectory(File directoryToBeDeleted) {
File[] allContents = directoryToBeDeleted.listFiles();
if (allContents != null) {
for (File file : allContents) {
deleteDirectory(file);
}
}
return directoryToBeDeleted.delete();
}

rm -rf的性能更好。

经过大量的基准测试,我们发现使用rm -rf比使用FileUtils.deleteDirectory快几倍。

当然,如果你有一个小或简单的目录,这并不重要,但在我们的例子中,我们有多个gb和深嵌套的子目录,其中FileUtils.deleteDirectory需要超过10分钟,而rm -rf只需要1分钟。

下面是我们粗略的Java实现:

// Delete directory given and all subdirectories and files (i.e. recursively).
//
static public boolean deleteDirectory( File file ) throws IOException, InterruptedException {


if ( file.exists() ) {


String deleteCommand = "rm -rf " + file.getAbsolutePath();
Runtime runtime = Runtime.getRuntime();


Process process = runtime.exec( deleteCommand );
process.waitFor();


return true;
}


return false;


}

如果您正在处理大型或复杂的目录,值得一试。

番石榴提供了一行程序:MoreFiles.deleteRecursively()

与许多共享的示例不同,它只考虑符号链接,并且不会(默认情况下)删除所提供路径之外的文件。

// Java 8的lambda &如果参数为directory,则为流

static boolean delRecursive(File dir) {
return Arrays.stream(dir.listFiles()).allMatch((f) -> f.isDirectory() ? delRecursive(f) : f.delete()) && dir.delete();
}

如果参数是文件或目录

static boolean delRecursive(File fileOrDir) {
return fileOrDir.isDirectory() ? Arrays.stream(fileOrDir.listFiles()).allMatch((f) -> delRecursive(f)) && fileOrDir.delete() : fileOrDir.delete();
}

番石榴21.0及以上版本

自Guava 21.0以来,有MoreFiles类的void deleteRecursively(Path path, RecursiveDeleteOption... options) throws IOException静态方法可用。

请参阅Javadoc文档:

公共静态无效deleteRecursively(路径路径, RecursiveDeleteOption……选项) 抛出IOException < / p >

递归地删除给定path处的文件或目录。删除符号链接,而不是它们的目标(根据下面的警告)。

如果试图读取、打开或删除给定目录下的任何文件时发生I/O异常,此方法将跳过该文件并继续。所有这样的异常都会被收集,并且在尝试删除所有文件后,会抛出IOException,其中包含这些异常作为抑制异常