java.nio.file.Files.delete(Path path) - случайный сбой при рекурсивном удалении каталога с использованием SimpleFileVisitor
Попытка устранить случайные java.nio.file.DirectoryNotEmptyException
в рекурсивном методе удаления, взятом из каталогов Delete рекурсивно в Java
Код (кредит @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)
требуется бросить? Документы утверждают:
В некоторых операционных системах может быть невозможно удалить файл, когда он открыт и используется этой виртуальной машиной Java или другими программами.
Похоже, не требует исключения в этом случае. Является java.nio.file.Files.delete(Path path)
требуется бросить?
4 ответа
У меня была та же самая проблема, и оказалось, что проблема была вызвана потоком файла незакрытого каталога где-то еще в коде для того же каталога, из которого я удалял материал. Потоковый объект, возвращаемый:
Files.list(Path)
должен быть закрыт, поэтому обязательно используйте конструкцию try-with-resources в вашем коде, если используете этот метод.
Так что я не думаю, что удаление занимает слишком много времени, я попытался подождать, прежде чем повторить попытку удаления каталога, но безуспешно. Скорее всего, ваша собственная программа заблокировала этот ресурс. В результате вызов удаления для него не завершается, хотя он успешно возвращается (похоже, что Windows в конечном итоге удалит файл, как только ваша собственная программа освободит его), но, конечно, содержащий каталог не может быть удален, так как это на самом деле еще нет пустой.
Я знаю, что это очень старая тема, но у меня была та же проблема, и мне потребовалось довольно много времени, чтобы исправить ее. Я думаю, что это неправильное поведение вызвано проблемой синхронизации (похоже, это происходит только в 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;
}
Энди
Я бы прокомментировал ответ @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());
}
}
Вы можете временно сохранить имя файла, который вы сейчас повторяете, в переменной и выполнить попытку DirectoryNotEmptyException
, Когда возникает это исключение, поймайте его и сгенерируйте свое собственное исключение, которое указывает файл.
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()
продолжает удалять файлы в каталоге, когда обнаруживает файл, который невозможно удалить. Если это так, мой метод все еще работает: просто верните список всех файлов в каталоге вместо имени файла каталога. Он должен содержать только файлы, которые нельзя удалить, и у вас все еще есть решение.
Расширение моего комментария выше.
Когда у Java возникает проблема с выполнением чего-либо, возникает исключение. Исключения, как и все другие типы, могут наследоваться от. API будет указывать, какие проверенные исключения выдает каждый метод. Методы в java.io
а также java.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;
}
Вы можете изменить фактический ответ программы так, как вам нужно, но это должно дать вам общее представление.