文件系統遍歷是 Java IO 常見需求。本文對比兩種經典實現:非遞歸廣度優先(隊列) 與 遞歸深度優先(棧/調用棧),並通過實測數據分析性能差異。
import java.io.File;
import java.util.LinkedList;
public class FileSystemBFS {
public static void main(String[] args) {
long start = System.currentTimeMillis();
LinkedList<File> queue = new LinkedList<>();
File root = new File("C:\\Program Files\\Java\\");
File[] files = root.listFiles();
if (files != null) {
for (File f : files) {
if (f.isDirectory()) queue.add(f);
else System.out.println(f.getAbsolutePath());
}
}
File current;
while (!queue.isEmpty()) {
current = queue.removeFirst();
File[] children = current.listFiles();
if (children == null) continue; // 權限不足等情況
for (File f : children) {
if (f.isDirectory()) queue.add(f);
else System.out.println(f.getAbsolutePath());
}
}
System.out.println("耗時: " + (System.currentTimeMillis() - start) + "ms");
}
}
import java.io.File;
import java.util.ArrayList;
public class FileSystemDFS {
private static ArrayList<String> fileList = new ArrayList<>();
public static void main(String[] args) {
long start = System.currentTimeMillis();
refreshFileList("C:\\Program Files\\Java\\");
System.out.println("耗時: " + (System.currentTimeMillis() - start) + "ms");
}
public static void refreshFileList(String path) {
File dir = new File(path);
File[] files = dir.listFiles();
if (files == null) return; // 關鍵防禦:權限不足返回 null
for (File f : files) {
if (f.isDirectory()) {
refreshFileList(f.getAbsolutePath()); // 遞歸調用
} else {
String name = f.getAbsolutePath().toLowerCase();
System.out.println("---" + name);
fileList.add(f.getAbsolutePath());
}
}
}
}
| 指標 | 非遞歸 BFS | 遞歸 DFS | 說明 |
|---|---|---|---|
| 執行時間 | ~1120 ms | ~980 ms | DFS 略快 10-15% |
| 內存峰值 | 較高(隊列佔用) | 較低(棧幀佔用) | 視目錄寬度/深度而定 |
| 代碼複雜度 | 中等 | 極低 | 遞歸更易維護 |
| 極深目錄安全性 | 安全 | 風險 | 需評估業務目錄深度 |
listFiles() == null:權限不足、非目錄、IO 錯誤均返回 null,直接遍歷會拋 NPE。Files.walkFileTree() (NIO.2) + 並行流,或使用 File.listFiles(FileFilter) 早期過濾。
Files.walkFileTree(Paths.get(rootPath), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
System.out.println(file.toAbsolutePath());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
// 統一錯誤處理
return FileVisitResult.CONTINUE;
}
});
優勢:自動處理循環鏈接、權限錯誤、屬性預取;支持 maxDepth 限制深度;Java 8+ 可配合 Stream API 並行處理。
遞歸 DFS 在常規目錄結構下性能略優且代碼最簡潔,是首選方案。非遞歸 BFS 適合極深目錄或需層級控制的場景。生產環境建議直接使用 Files.walkFileTree,獲得健壯性與現代 API 支持的雙重保障。