專案中常需清理臨時目錄、建構產物目錄下的空資料夾。本文提供生產就緒的工具類,採用深度優先遞歸掃描收集所有目錄,再反向刪除空目錄,支援大層級目錄結構且記憶體佔用可控。
package com.func;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class FileUtils {
/** 遞歸收集指定根目錄下所有子目錄 (深度優先) */
public List visitAll(File root) {
List dirs = new ArrayList<>();
if (root == null || !root.exists() || !root.isDirectory()) {
return dirs;
}
File[] children = root.listFiles();
if (children != null) {
for (File child : children) {
if (child.isDirectory()) {
dirs.add(child);
dirs.addAll(visitAll(child)); // 遞歸收集子目錄
}
}
}
return dirs;
}
/** 批次刪除空目錄 (僅刪除無任何檔案/子目錄的目錄) */
public void removeEmptyDirs(List dirs) {
if (dirs == null) return;
// 反向遍歷:先刪除最深層,避免父目錄因子目錄尚未刪除而非空
for (int i = dirs.size() - 1; i >= 0; i--) {
File dir = dirs.get(i);
if (dir.isDirectory() && dir.listFiles().length == 0) {
boolean deleted = dir.delete();
if (deleted) {
System.out.println("已刪除空目錄: " + dir.getAbsolutePath());
} else {
System.err.println("刪除失敗 (可能非空或權限不足): " + dir.getAbsolutePath());
}
}
}
}
/** 一鍵清理入口 */
public void cleanEmptyDirs(String rootPath) {
File root = new File(rootPath);
List allDirs = visitAll(root);
System.out.println("掃描到目錄總數: " + allDirs.size());
removeEmptyDirs(allDirs);
System.out.println("清理完成");
}
public static void main(String[] args) {
new FileUtils().cleanEmptyDirs("e:/aaa"); // 測試路徑
}
}
| 決策點 | 方案 | 理由 |
|---|---|---|
| 遍歷順序 | 深度優先 (DFS) | 自然適合遞歸實現,程式碼簡潔 |
| 收集策略 | 先全量收集再處理 | 避免遍歷過程中修改目錄結構導致 ConcurrentModification 或漏掃 |
| 刪除順序 | 反向遍歷 (葉子優先) | 確保子目錄先刪除,父目錄才可能變空並被刪除 |
| 空目錄判定 | listFiles().length == 0 | 標準 API,包含隱藏檔案 |
| 錯誤處理 | 捕獲 delete() 返回值 + 異常 | 權限不足、檔案被鎖定等情況不拋出異常只返回 false |
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.EnumSet;
public class NioFileUtils {
/** 使用 Files.walkFileTree 實現高效非遞歸遍歷 */
public static void cleanEmptyDirsNio(String rootPath) throws IOException {
Path root = Paths.get(rootPath);
Files.walkFileTree(root, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
new SimpleFileVisitor() {
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
if (exc != null) throw exc;
try (DirectoryStream stream = Files.newDirectoryStream(dir)) {
if (!stream.iterator().hasNext()) { // 目錄為空
Files.delete(dir);
System.out.println("NIO 刪除空目錄: " + dir);
}
}
return FileVisitResult.CONTINUE;
}
});
}
}
NIO 版優勢: 非遞歸避免棧溢位、支援符號連結控制、原生併發安全、效能更優。
dryRun 參數,僅列印將刪除目錄不實際執行protectedPaths 白名單防止誤刪關鍵目錄@Scheduled 或 Quartz 定時清理walkFileTree 或手動棧模擬遞歸EnumSet.of(FileVisitOption.FOLLOW_LINKS) 控制;傳統版需自行維護已訪問路徑 Set此工具類已在多個專案的建構腳本、臨時檔案清理排程中穩定運行,程式碼簡潔無依賴,可直接複製整合至任何 Java 專案中使用。