Java : ディレクトリを丸ごと削除
標準APIの Files.delete は、空のディレクトリは削除できますが、中身のあるディレクトリは削除できません。
本記事では、中身のあるディレクトリを丸ごと削除する方法をご紹介します。
コード例
さっそくコード例を見てみましょう。
public void deleteAll(Path dir) throws IOException {
Files.walkFileTree(dir, new SimpleFileVisitor<>() {
@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 {
if (exc != null) {
throw exc;
}
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
deleteAll メソッドのパラメータ dir に削除したいディレクトリを指定すると、その中身のファイルやサブディレクトリも含めてすべて削除します。
上記のコード例は、公式API仕様の FileVisitor の使用例を参考にしています。
注意
- 大切なファイルを削除してしまわないように、十分に注意して動作確認は行ってください。
解説
標準API の Files.delete は、
- ファイル
- 空のディレクトリ
を削除できます。
しかし、空ではないディレクトリは削除できません。
例えば、次のようなファイル構成があったとします。
(コマンドは Windows の PowerShell で実行しています)
PS D:\java-work> tree /F
...
D:.
│ aaa.txt
│
├─dir1
└─dir2
bbb.txt
- aaa.txt : ファイル
- dir1 : 空のディレクトリ
- dir2 : 中身(bbb.txt)のある、空ではないディレクトリ
これらに対して Files.delete を実行してみましょう。
final var aaa = Path.of("D:", "java-work", "aaa.txt");
System.out.println(aaa); // D:\java-work\aaa.txt
System.out.println(Files.isRegularFile(aaa)); // true
// 削除OK
Files.delete(aaa);
System.out.println(Files.notExists(aaa)); // true
final var dir1 = Path.of("D:", "java-work", "dir1");
System.out.println(dir1); // D:\java-work\dir1
System.out.println(Files.isDirectory(dir1)); // true
// 削除OK
Files.delete(dir1);
System.out.println(Files.notExists(dir1)); // true
final var dir2 = Path.of("D:", "java-work", "dir2");
System.out.println(dir2); // D:\java-work\dir2
System.out.println(Files.isDirectory(dir2)); // true
try {
// 削除NG
Files.delete(dir2);
} catch (IOException e) {
System.out.println(e);
}
// 結果
// ↓
//java.nio.file.DirectoryNotEmptyException: D:\java-work\dir2
PS D:\java-work> tree /F
...
D:.
└─dir2
bbb.txt
aaa.txt と dir1 は削除できました。
しかし、dir2 を削除しようとすると DirectoryNotEmptyException が発生します。
そのため、ディレクトリを削除するには…
まずそのディレクトリ内のファイルおよびディレクトリをすべて削除して空にする必要があります。
次は Files.walkFileTree です。
walkFileTree は簡単にいうと、指定したパスのファイルおよびディレクトリの一覧に順にアクセスしていきます。
例えば、次のようなファイル構成があるとします。
PS D:\java-work> tree /F
...
D:.
└─target-dir
│ aaa.txt
│ bbb.txt
│
├─dir1
│ │ ccc.txt
│ │ ddd.txt
│ │
│ └─dir1-2
│ eee.txt
│ fff.txt
│
└─dir2
ggg.txt
hhh.txt
Files.walkFileTree で、target-dir のファイルおよびディレクトリのすべてにアクセスしてみましょう。
アクセスするためには、FileVisitor を指定してコールバックを受け取ります。
今回は FileVisitor のサブクラスである SimpleFileVisitor を使います。
final var target = Path.of("D:", "java-work", "target-dir");
System.out.println(target); // D:\java-work\target-dir
System.out.println(Files.isDirectory(target)); // true
Files.walkFileTree(target, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
System.out.println("visitFile : " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
System.out.println("postVisitDirectory : " + dir);
return FileVisitResult.CONTINUE;
}
});
// 結果
// ↓
//visitFile : D:\java-work\target-dir\aaa.txt
//visitFile : D:\java-work\target-dir\bbb.txt
//visitFile : D:\java-work\target-dir\dir1\ccc.txt
//visitFile : D:\java-work\target-dir\dir1\ddd.txt
//visitFile : D:\java-work\target-dir\dir1\dir1-2\eee.txt
//visitFile : D:\java-work\target-dir\dir1\dir1-2\fff.txt
//postVisitDirectory : D:\java-work\target-dir\dir1\dir1-2
//postVisitDirectory : D:\java-work\target-dir\dir1
//visitFile : D:\java-work\target-dir\dir2\ggg.txt
//visitFile : D:\java-work\target-dir\dir2\hhh.txt
//postVisitDirectory : D:\java-work\target-dir\dir2
//postVisitDirectory : D:\java-work\target-dir
FileVisitor の各種メソッドは、下記のタイミングでコールバックされます。
- visitFile : ファイルが見つかったとき
- postVisitDirectory : ディレクトリ内のすべてのファイルおよびディレクトリのコールバックが終わったとき
つまり、visitFile でファイルを削除していけば、postVisitDirectory が呼び出されたときは、そのディレクトリは空となります。
補足
- Files.walkFileTree は、デフォルトではシンボリックリンクのディレクトリはたどりません。
あまりないとは思いますが、もしシンボリックリンクの先も含めてアクセスしたい場合は、FileVisitOption.FOLLOW_LINKS を指定します。
使用例については「Files (ファイル操作)」の記事をご参照ください。
まとめ
最後に、ちょっとしたユーティリティクラスとしてまとめてみました。
public class FileUtils {
private FileUtils() {
}
public static void deleteAll(Path dir) throws IOException {
Objects.requireNonNull(dir);
Files.walkFileTree(dir, new SimpleFileVisitor<>() {
@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 {
if (exc != null) {
throw exc;
}
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
}
下記のファイル構成で、target-dir ディレクトリを削除してみます。
// --- PowerShell ---
//PS D:\java-work> tree /F
//...
//D:.
//└─target-dir
// │ aaa.txt
// │ bbb.txt
// │
// ├─dir1
// │ │ ccc.txt
// │ │ ddd.txt
// │ │
// │ └─dir1-2
// │ eee.txt
// │ fff.txt
// │
// └─dir2
// ggg.txt
// hhh.txt
final var dir = Path.of("D:", "java-work", "target-dir");
System.out.println(dir); // D:\java-work\target-dir
System.out.println(Files.isDirectory(dir)); // true
FileUtils.deleteAll(dir);
System.out.println(Files.notExists(dir)); // true
// --- PowerShell ---
//PS D:\java-work> tree /F
//...
//D:.
//サブフォルダーは存在しません
無事に、指定したディレクトリを丸ごと削除できました。