首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 目錄遞迴遍歷實作:Iterator 模式深度解析

     編輯:Java 相關知識

優雅遍歷檔案系統:自訂 Iterator 實現深度優先目錄走訪

Java NIO.2 的 Files.walkFileTree 雖然強大,但理解如何手工實作 Iterator<File> 進行目錄遞迴遍歷,有助於掌握迭代器模式與檔案系統操作精髓。本範例實作一個可疊代的 FileIterator,支援多根目錄、深度優先走訪、標準 for-each 語法。

設計模式應用

  • Iterator 模式:封裝遍歷邏輯,統一存取介面
  • Iterable 介面:支援增強 for 迴圈(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

與 NIO.2 Files.walkFileTree 比較

特性自訂 FileIteratorFiles.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; }
}

最佳實踐建議

  1. 生產環境優先使用 NIO.2:Files.walkFileTree 或 Files.find 更健壯
  2. 大量檔案處理:使用 Stream<Path> 搭配 try-with-resources 自動關閉
  3. 權限處理:捕獲 AccessDeniedException 並記錄而非中斷
  4. 符號連結:維護已訪問 realPath 集合防止循環
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved