Есть ли способ рекурсивно удалять целые каталоги в Java?
В обычном случае можно удалить пустой каталог. Однако когда дело доходит до удаления целых каталогов с содержимым, это уже не так просто.
Как удалить целые каталоги с содержимым в Java?
Есть ли способ рекурсивно удалять целые каталоги в Java?
В обычном случае можно удалить пустой каталог. Однако когда дело доходит до удаления целых каталогов с содержимым, это уже не так просто.
Как удалить целые каталоги с содержимым в Java?
Вам следует проверить Apache commons-io. У него есть FileUtils, который будет делать то, что вы хотите.
FileUtils.deleteDirectory(new File("directory"));
С Java 7 мы наконец можем сделать это с помощью надежного обнаружения символических ссылок. (я не считаю, что Apache commons-io имеет надежное обнаружение символических ссылок в настоящее время, поскольку оно не обрабатывает ссылки в Windows, созданные с помощью mklink
.)
Ради истории, вот ответ до 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);
}
foo
со ссылкой foo/link
, так что link->/
, вызов delete(new File(foo))
удалит столько файловой системы, сколько разрешено вашему пользователю !!
- person Miquel; 07.06.2013
В 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;
}
});
super.postVisitDirectory(dir, exc);
в вашем postVisitDirectory
методе, чтобы взорвать, если обход не может перечислить каталог.
- person Tom Anderson; 21.03.2019
Однострочное решение (Java8) для рекурсивного удаления всех файлов и каталогов, включая начальный каталог:
Files.walk(Paths.get("c:/dir_to_delete/"))
.map(Path::toFile)
.sorted((o1, o2) -> -o1.compareTo(o2))
.forEach(File::delete);
Мы используем компаратор для обратного порядка, иначе File :: delete не сможет удалить, возможно, непустой каталог. Итак, если вы хотите сохранить каталоги и удалять только файлы, просто удалите компаратор в sorted () или полностью удалите сортировку и добавьте фильтр файлов:
Files.walk(Paths.get("c:/dir_to_delete/"))
.filter(Files::isRegularFile)
.map(Path::toFile)
.forEach(File::delete);
.sorted(Comparator.reverseOrder())
Предложение Comparator::reverseOrder
не. См .: stackoverflow.com/questions/43036611/
- person user1156544; 09.05.2018
.sorted((f1, f2) -> f2.compareTo(f1))
, сравнивая f2
с f1
вместо f1
с f2
.
- person Beto Neto; 17.02.2020
walk()
, должен быть закрыт, потому что он содержит ссылки на один или несколько открытых каталогов (Javadoc).
- person rolve; 18.09.2020
В 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. Процессы являются частными, но их поведение должно быть очевидным.)
Только что увидел, что мое решение более или менее похоже на решение Эриксона, только упаковано как статический метод. Бросьте это куда-нибудь, это намного легче, чем установка всех 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();
}
}
Решение со стеком и без рекурсивных методов:
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();
}
}
list*
методами для класса java.io.File
. Из документации Javadocs: возвращает null, если это абстрактное имя пути не обозначает каталог или если возникает ошибка ввода-вывода. Итак: if (currList.length > 0) {
становится if (null != currList && currList.length > 0) {
- person kevinarpe; 11.11.2013
Guava имел _ 1_ поддерживается до Guava 9.
Из Гуава 10:
Устарело. Этот метод плохо обнаруживает символические ссылки и условия гонки. Эта функциональность может поддерживаться надлежащим образом только с помощью командного интерпретатора операционной системы, такой как
rm -rf
илиdel /s
. Этот метод планируется удалить из Guava в выпуске Guava 11.0.
Следовательно, такого метода нет в Guava 11.
Если у вас есть Spring, вы можете использовать FileSystemUtils.deleteRecursively:
import org.springframework.util.FileSystemUtils;
boolean success = FileSystemUtils.deleteRecursively(new File("directory"));
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) { /* ... */ }
});
Files.walk(path).iterator().toSeq.reverse.foreach(Files.delete)
- person James Ward; 19.02.2016
walk
уже гарантирует обход в глубину.
- person VGR; 01.09.2016
Collections.reverseOrder()
, поэтому ваш код будет for (Path p : Files.walk(directoryToDelete).sorted(reverseOrder()).toArray(Path[]::new))
, если он был импортирован статически.
- person namero999; 06.10.2016
Comparator.reverseOrder
? Files.walk(dir) .sorted(Comparator.reverseOrder()) .toArray(Path[]::new))
- person Jeff; 23.02.2017
Два способа потерпеть неудачу с символическими ссылками и приведенным выше кодом ... и не знаю решения.
Запустите это, чтобы создать тест:
echo test > testfile
mkdir dirtodelete
ln -s badlink dirtodelete/badlinktodelete
Здесь вы видите свой тестовый файл и тестовый каталог:
$ ls testfile dirtodelete
testfile
dirtodelete:
linktodelete
Затем запустите свой commons-io deleteDirectory (). Вылетает, говоря, что файл не найден. Не уверен, что здесь делают другие примеры. Команда Linux rm просто удалит ссылку, а также rm -r в каталоге.
Exception in thread "main" java.io.FileNotFoundException: File does not exist: /tmp/dirtodelete/linktodelete
Запустите это, чтобы создать тест:
mkdir testdir
echo test > testdir/testfile
mkdir dirtodelete
ln -s ../testdir dirtodelete/dirlinktodelete
Здесь вы видите свой тестовый файл и тестовый каталог:
$ ls dirtodelete testdir
dirtodelete:
dirlinktodelete
testdir:
testfile
Затем запустите свой commons-io deleteDirectory () или пример кода, опубликованный людьми. Он удаляет не только каталог, но и ваш тестовый файл, который находится за пределами удаляемого каталога. (Он неявно разыменовывает каталог и удаляет его содержимое). rm -r удалит только ссылку. Вам нужно использовать что-то вроде этого для удаления разыменованных файлов: "find -L dirtodelete -type f -exec rm {} \;".
$ ls dirtodelete testdir
ls: cannot access dirtodelete: No such file or directory
testdir:
Вы можете использовать:
org.apache.commons.io.FileUtils.deleteQuietly(destFile);
Удаляет файл, не вызывая исключения. Если файл является каталогом, удалите его и все подкаталоги. Разница между File.delete () и этим методом заключается в следующем: удаляемый каталог не должен быть пустым. Если файл или каталог не могут быть удалены, исключения не создаются.
Оптимальное решение, которое обрабатывает исключение в соответствии с подходом, согласно которому исключение, созданное методом, всегда должно описывать, что этот метод пытался (и не смог) сделать:
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);
}
}
В старых проектах мне нужно создавать собственный код 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());
}
}
Приведенный ниже код рекурсивно удаляет все содержимое в данной папке.
boolean deleteDirectory(File directoryToBeDeleted) {
File[] allContents = directoryToBeDeleted.listFiles();
if (allContents != null) {
for (File file : allContents) {
deleteDirectory(file);
}
}
return directoryToBeDeleted.delete();
}
Вот простой основной метод, который принимает аргумент командной строки, вам может потребоваться добавить свою собственную проверку ошибок или сформировать ее так, как вы считаете нужным.
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();
}
}
}
Надеюсь, это поможет!
Возможно, решением этой проблемы может быть повторная реализация метода удаления класса File с использованием кода из ответа Эриксона:
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();
}
}
}
Без Commons 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 8 с лямбдой и потоком, если параметр - это каталог
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();
}
Хотя файлы можно легко удалить с помощью 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();
}
}
}
}
}
}
Я закодировал эту процедуру, которая имеет 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);
}
}
rm -rf
был намного эффективнее, чем FileUtils.deleteDirectory
.После обширного тестирования мы обнаружили, что использование rm -rf
было в несколько раз быстрее, чем использование FileUtils.deleteDirectory
.
Конечно, если у вас небольшой или простой каталог, это не имеет значения, но в нашем случае у нас было несколько гигабайт и глубоко вложенные подкаталоги, где это заняло бы более 10 минут с FileUtils.deleteDirectory
и только 1 минуту с rm -rf
.
Вот примерная реализация этого на 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;
}
Стоит попробовать, если вы имеете дело с большими или сложными каталогами.
Что ж, давайте возьмем пример,
import java.io.File;
import java.io.IOException;
public class DeleteDirectory
{
private static final String folder = "D:/project/java";
public static void main(String[] args) throws IOException
{
File fl = new File(folder);
if(!fl.exists()) // checking if directory exists
{
System.out.println("Sorry!! directory doesn't exist.");
}
else
{
DeleteDirectory dd = new DeleteDirectory();
dd.deleteDirectory(fl);
}
}
public void deleteDirectory(File file) throws IOException
{
if(file.isDirectory())
{
if(file.list().length == 0)
{
deleteEmptyDirectory(file); // here if directory is empty delete we are deleting
}
else
{
File fe[] = file.listFiles();
for(File deleteFile : fe)
{
deleteDirectory(deleteFile); // recursive call
}
if(file.list().length == 0)
{
deleteEmptyDirectory(file);
}
}
}
else
{
file.delete();
System.out.println("File deleted : " + file.getAbsolutePath());
}
}
private void deleteEmptyDirectory(File fi)
{
fi.delete();
System.out.println("Directory deleted : " + fi.getAbsolutePath());
}
}
Для получения дополнительной информации см. Ресурсы ниже
f.delete()
под deleteDirectory(f)
вызовет исключение NoSuchFileException, поскольку deleteDirectory(f)
уже удаляет этот файл. Каждый каталог станет путем при передаче в deleteDirectory(f)
и удалении path.delete()
. Следовательно, нам не нужно f.delete()
в разделе if f.isDerectory
. Итак, просто удалите f.delete();
из каталога deleteDirectory (f), и он заработает.
- person Trieu Nguyen; 18.05.2020