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

Java 計算資料夾大小六種實現方案:從遞歸到 ForkJoin 全景解析

     編輯:Java 程式設計入門

需求:遞歸遍歷目錄樹,累加所有檔案大小

測試目錄:C:\Documents and Settings\Administrator\桌面\monkeytalk

方案一:單執行緒遞歸(基準版)


private long getTotalSizeOfFilesInDir(final File file) {
    if (file.isFile()) return file.length();
    final File[] children = file.listFiles();
    long total = 0;
    if (children != null)
        for (final File child : children)
            total += getTotalSizeOfFilesInDir(child);
    return total;
}
  • 優點:程式碼最簡潔、易理解
  • 缺點:無法利用多核、深層目錄棧溢位風險

方案二:FixedThreadPool + Callable + Future(任務分發版)


class SubDirectoriesAndSize {
    final long size;
    final List subDirectories;
    // 建構子...
}

private SubDirectoriesAndSize getTotalAndSubDirs(final File file) {
    long total = 0;
    List subDirectories = new ArrayList<>();
    if (file.isDirectory()) {
        for (File child : file.listFiles()) {
            if (child.isFile()) total += child.length();
            else subDirectories.add(child);
        }
    }
    return new SubDirectoriesAndSize(total, subDirectories);
}

private long getTotalSizeOfFilesInDir(final File file) throws ... {
    ExecutorService service = Executors.newFixedThreadPool(100);
    try {
        long total = 0;
        List directories = new ArrayList<>();
        directories.add(file);
        while (!directories.isEmpty()) {
            List> partialResults = new ArrayList<>();
            for (File directory : directories) {
                partialResults.add(service.submit(() -> getTotalAndSubDirs(directory)));
            }
            directories.clear();
            for (Future f : partialResults) {
                SubDirectoriesAndSize r = f.get(100, TimeUnit.SECONDS);
                directories.addAll(r.subDirectories);
                total += r.size;
            }
        }
        return total;
    } finally { service.shutdown(); }
}

策略: 層級並行,每層目錄並發處理,收集子目錄供下一輪。

方案三:遞歸提交 Callable(簡潔版)


private long getTotalSizeOfFilesInDir(ExecutorService service, File file) throws ... {
    if (file.isFile()) return file.length();
    long total = 0;
    File[] children = file.listFiles();
    if (children != null) {
        List> futures = new ArrayList<>();
        for (File child : children) {
            futures.add(service.submit(() -> getTotalSizeOfFilesInDir(service, child)));
        }
        for (Future f : futures) total += f.get(100, TimeUnit.SECONDS);
    }
    return total;
}

缺點: 遞歸深度大時會建立大量 Future,記憶體壓力大。

方案四:CountDownLatch + AtomicLong(計數器協調版)


final AtomicLong pendingFileVisits = new AtomicLong();
final AtomicLong totalSize = new AtomicLong();
final CountDownLatch latch = new CountDownLatch(1);

private void updateTotalSizeOfFilesInDir(final File file) {
    long fileSize = 0;
    if (file.isFile()) fileSize = file.length();
    else {
        for (File child : file.listFiles()) {
            if (child.isFile()) fileSize += child.length();
            else {
                pendingFileVisits.incrementAndGet();
                service.execute(() -> updateTotalSizeOfFilesInDir(child));
            }
        }
    }
    totalSize.addAndGet(fileSize);
    if (pendingFileVisits.decrementAndGet() == 0) latch.countDown();
}

機制: pendingFileVisits 追蹤進行中任務數,歸零時觸發 latch.countDown()。

方案五:BlockingQueue + AtomicLong(生產者消費者版)


final BlockingQueue fileSizes = new ArrayBlockingQueue<>(500);
final AtomicLong pendingFileVisits = new AtomicLong();

private void exploreDir(final File file) {
    long fileSize = 0;
    if (file.isFile()) fileSize = file.length();
    else {
        for (File child : file.listFiles()) {
            if (child.isFile()) fileSize += child.length();
            else startExploreDir(child); // 遞歸提交子目錄
        }
    }
    fileSizes.put(fileSize); // 生產大小
    pendingFileVisits.decrementAndGet();
}

// 消費者迴圈
while (pendingFileVisits.get() > 0 || fileSizes.size() > 0) {
    Long size = fileSizes.poll(10, TimeUnit.SECONDS);
    totalSize += size;
}

優勢: 解耦遍歷與累加,背壓自然形成(隊列滿即阻塞)。

方案六:JDK 7 ForkJoinPool(分治法標準實作)


private static class FileSizeFinder extends RecursiveTask {
    final File file;
    public FileSizeFinder(File file) { this.file = file; }
    
    @Override
    protected Long compute() {
        long size = 0;
        if (file.isFile()) return file.length();
        File[] children = file.listFiles();
        if (children != null) {
            List> tasks = new ArrayList<>();
            for (File child : children) {
                if (child.isFile()) size += child.length();
                else tasks.add(new FileSizeFinder(child));
            }
            for (ForkJoinTask task : invokeAll(tasks)) size += task.join();
        }
        return size;
    }
}

// 使用
ForkJoinPool pool = new ForkJoinPool();
long total = pool.invoke(new FileSizeFinder(new File(path)));

核心: RecursiveTask 分治,invokeAll 並行執行子任務,join 合併結果。工作竊取演算法自動負載均衡。

方案對比總結

方案程式複雜度記憶體適用場景
單執行緒遞歸最低最低小目錄、教學、快速原型
FixedThreadPool+Future中中淺層寬目錄樹
遞歸 Callable低高深層目錄慎用
CountDownLatch中高中需精確控制完成時機
BlockingQueue中可控流式處理、背壓需求
ForkJoinPool中高效通用首選、JDK 7+

生產建議

  1. JDK 7+ 直接用 ForkJoinPool 或 Files.walkFileTree (NIO.2)
  2. 超大目錄考慮 Files.walk 串流 + 並行 parallel()
  3. 注意 File.listFiles() 可能回傳 null(權限不足)
  4. 生產環境加入超時、異常重試、進度回報
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved