Java NIO.2 的 Files.walkFileTree 雖然強大,但理解如何手工實作 Iterator<File> 進行目錄遞迴遍歷,有助於掌握迭代器模式與檔案系統操作精髓。本範例實作一個可疊代的 FileIterator,支援多根目錄、深度優先走訪、標準 for-each 語法。
for (File f : iterator))package archiver.util;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* 檔案系統深度優先遍歷迭代器
* 支援多根目錄、標準 for-each 語法
*/
public class FileIterator implements Iterator, Iterable {
private Iterator pathIterator; // 當前路徑迭代器
private List paths; // 待處理路徑佇列
private List directories; // 發現的子目錄堆疊
/**
* 建構子:接受多個根路徑
* @param rootPaths 根目錄路徑列表
*/
public FileIterator(List rootPaths) {
this.paths = new ArrayList<>(rootPaths);
this.directories = new ArrayList<>();
this.pathIterator = this.paths.iterator();
}
/**
* 取得下一個檔案/目錄
* @return File 物件,無更多元素回傳 null
*/
@Override
public File next() {
File file = null;
if (hasNext()) {
file = new File(pathIterator.next());
// 若為目錄,加入待展開堆疊
if (file.isDirectory()) {
directories.add(file);
}
}
return file;
}
/**
* 檢查是否有下一個元素(核心遞迴邏輯)
* @return true 表示有下一個元素
*/
@Override
public boolean hasNext() {
// 當前路徑迭代器耗盡時,展開下一層目錄
if (!pathIterator.hasNext()) {
paths.clear();
// 遍歷所有發現的目錄,收集其子項路徑
for (File dir : directories) {
String[] children = dir.list();
if (children != null) { // 防護:權限不足或非目錄時 list() 回傳 null
for (String name : children) {
paths.add(dir.getPath() + File.separator + name);
}
}
}
directories.clear();
// 無更多路徑表示遍歷結束
if (paths.isEmpty()) {
return false;
}
// 重建迭代器繼續遍歷
pathIterator = paths.iterator();
}
return true;
}
@Override
public void remove() {
throw new UnsupportedOperationException("不支援移除操作");
}
@Override
public Iterator iterator() {
return this; // 自己就是迭代器,支援 for-each
}
// 測試主程式
public static void main(String[] args) {
// 遍歷當前目錄
FileIterator iter = new FileIterator(Arrays.asList("."));
for (File file : iter) {
System.out.println(file.getAbsolutePath());
}
}
}
初始狀態:paths=["."] → iterator 指向 "."
│
├─ next() → File(".") → 是目錄 → directories=["."]
│
├─ hasNext() → iterator 無下一個
│ │
│ ├─ paths.clear()
│ ├─ 遍歷 directories["."] → list() → ["file1.txt", "subdir"]
│ ├─ paths ← ["file1.txt", "subdir"]
│ ├─ directories.clear()
│ └─ iterator ← paths.iterator()
│
├─ next() → File("file1.txt") → 非目錄
├─ next() → File("subdir") → 是目錄 → directories=["subdir"]
│
├─ hasNext() → iterator 無下一個
│ └─ 展開 "subdir" 子項...
│
└─ 直到 paths 為空 → hasNext() 回傳 false
| 特性 | 自訂 FileIterator | Files.walkFileTree |
|---|---|---|
| 程式碼複雜度 | ~50 行 | 需實作 FileVisitor(~30 行) |
| 遍歷順序控制 | 深度優先(固定) | 深度優先,可控制預訂/後訂 |
| 錯誤處理 | 簡單(list() 回傳 null) | 完整(visitFileFailed 回呼) |
| 符號連結處理 | 無(可能無限迴圈) | 支援 FOLLOW_LINKS 選項 |
| 效能(大量檔案) | 較低(ArrayList 擴容) | 優化(內部迭代器) |
| 記憶體佔用 | O(目錄寬度) | O(樹深度) |
public class AdvancedFileIterator implements Iterator, Iterable {
private final int maxDepth;
private final FileFilter filter;
private final Deque stack = new ArrayDeque<>();
private File nextFile = null;
public AdvancedFileIterator(File root, int maxDepth, FileFilter filter) {
this.maxDepth = maxDepth;
this.filter = filter;
if (root != null && (maxDepth < 0 || maxDepth > 0)) {
stack.push(root);
}
advance();
}
private void advance() {
while (!stack.isEmpty()) {
File current = stack.pop();
int depth = getDepth(current); // 需額外追蹤深度
if (filter == null || filter.accept(current)) {
nextFile = current;
return;
}
if (current.isDirectory() && (maxDepth < 0 || depth < maxDepth)) {
File[] children = current.listFiles();
if (children != null) {
// 反序推入以維持自然順序
for (int i = children.length - 1; i >= 0; i--) {
stack.push(children[i]);
}
}
}
}
nextFile = null;
}
@Override public boolean hasNext() { return nextFile != null; }
@Override public File next() { File f = nextFile; advance(); return f; }
}
Files.walkFileTree 或 Files.find 更健壯Stream<Path> 搭配 try-with-resources 自動關閉AccessDeniedException 並記錄而非中斷realPath 集合防止循環