Немного странное поведение Files.delete и Files.deleteIfExists
Я получил такой код:
paths.forEach(folderPath -> {
Path to = folderPath.getRoot().resolve(folderPath.getParent().subpath(0, folderPath.getNameCount() - 1)); // До имени (исключительно)
try {
Files.list(folderPath).forEach(filePath -> {
try { Files.move(filePath, to.resolve(filePath.getFileName()), StandardCopyOption.ATOMIC_MOVE); }
catch (IOException e) { processException(e); }
});
if (Files.list(folderPath).count() == 0)
Files.deleteIfExists(folderPath); // this call
} catch (IOException e) { processException(e); }
});
После вызова методов удаления я блокирую пустой каталог (сразу после его вызова проверил), но не удаляю до закрытия приложения. Я нахожу это немного странным, но хочу знать, почему это происходит.
(Я использую Windows 10)
1 ответ
Из документации Files.list(Path)
:
Этот метод должен использоваться внутри оператора try-with-resources или аналогичной структуры управления, чтобы гарантировать, что открытый каталог потока будет закрыт сразу после завершения операций потока.
Вы этого не делаете, поэтому применяется следующая часть Files.deleteIfExists(…):
В некоторых операционных системах может быть невозможно удалить файл, когда он открыт и используется этой виртуальной машиной Java или другими программами.
Вы должны использовать
paths.forEach(folderPath -> {
Path to = folderPath.getParent();
try {
try(Stream<Path> files = Files.list(folderPath)) {
files.forEach(filePath -> {
try{Files.move(filePath, to.resolve(filePath.getFileName()), ATOMIC_MOVE);}
catch (IOException e) { processException(e); }
});
}
try {
Files.deleteIfExists(folderPath);
} catch(DirectoryNotEmptyException ex) {
// may happen as you continue when Files.move fails,
// but you already reported the original exception then
}
} catch (IOException e) { processException(e); }
});
Это закрывает поток файлов перед попыткой удалить каталог. Обратите внимание, что операция второго потока была удалена, этот вид предварительной проверки является расточительным и должен быть ненужным, когда все move
операция прошла успешно. Но если какое-то другое приложение вставляет новый файл одновременно, нет никакой гарантии, что это не произойдет между вашим Files.list(folderPath).count() == 0
проверка и последующее deleteIfExists
вызов.
Более чистым решением было бы помнить, когда move
не удалось. Когда нет move
не удалось, все еще не пустой каталог следует рассматривать как ошибочную ситуацию, о которой следует сообщать, как о любой другой ошибке, например
paths.forEach(folderPath -> {
Path to = folderPath.getParent();
try {
boolean allMovesSucceeded;
try(Stream<Path> files = Files.list(folderPath)) {
allMovesSucceeded = files
.map(filePath -> {
try {
Files.move(filePath, to.resolve(filePath.getFileName()), ATOMIC_MOVE);
return true;
}
catch(IOException e) { processException(e); return false; }
}).reduce(Boolean.TRUE, Boolean::logicalAnd);
}
if(allMovesSucceeded) Files.deleteIfExists(folderPath);
} catch (IOException e) { processException(e); }
});