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

PHP 取得資料夾檔案總數:遞歸與非遞歸、效能優化完整指南

     編輯:PHP 相關知識

統計資料夾檔案數量是常見需求(上傳限額、備份監控、清理策略)。PHP 提供多種方式,各有效能與功能差異。本文比較 scandir、glob、DirectoryIterator、RecursiveDirectoryIterator、系統指令,並提供生產環境可用的封裝函數。

方法比較總覽

方法遞歸記憶體速度特點
scandir()否高(全載入陣列)快簡單,需自行過濾 . 和 ..
glob()否高中支援模式匹配(*.jpg),大量檔案較慢
DirectoryIterator否低(逐一迭代)快SPL 物件導向,可直接判斷型別、大小、時間
RecursiveDirectoryIterator是低中遞歸遍歷整棵目錄樹
exec('find ...')是低最快Linux/Unix 專用,需 exec 權限,路徑需跳脫

1. 非遞歸:單層目錄檔案數

<?php
/**
 * 計算單層目錄檔案數(不含子目錄)
 * @param string $dir 目錄絕對路徑
 * @param bool   $includeHidden 是否包含隱藏檔(以 . 開頭)
 * @return int|false 成功回傳計數,失敗回傳 false
 */
function countFilesFlat(string $dir, bool $includeHidden = false): int|false
{
    if (!is_dir($dir)) return false;

    $count = 0;
    $iterator = new DirectoryIterator($dir);
    foreach ($iterator as $info) {
        if ($info->isDot()) continue;
        if (!$includeHidden && $info->getFilename()[0] === '.') continue;
        if ($info->isFile() || $info->isLink()) $count++;
    }
    return $count;
}

// 使用
$num = countFilesFlat('/var/www/uploads');
echo "檔案數: $num";
?>

2. 遞歸:含所有子目錄檔案總數

<?php
/**
 * 遞歸計算目錄樹總檔案數
 * @param string $dir
 * @param bool   $includeHidden
 * @param bool   $followLinks 是否跟隨符號連結(預設 false,防無限迴圈)
 * @return int|false
 */
function countFilesRecursive(string $dir, bool $includeHidden = false, bool $followLinks = false): int|false
{
    if (!is_dir($dir)) return false;

    $count = 0;
    $flags = RecursiveDirectoryIterator::SKIP_DOTS;
    if ($followLinks) $flags |= RecursiveDirectoryIterator::FOLLOW_SYMLINKS;

    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($dir, $flags),
        RecursiveIteratorIterator::LEAVES_ONLY // 只遍歷葉子節點(檔案),不進入目錄節點
    );

    foreach ($iterator as $info) {
        if (!$includeHidden && $info->getFilename()[0] === '.') continue;
        if ($info->isFile() || $info->isLink()) $count++;
    }
    return $count;
}

// 使用
$total = countFilesRecursive('/var/www/project');
echo "專案總檔案數: $total";
?>

3. 高效能:系統指令(Linux/Unix/macOS)

<?php
/**
 * 使用 find + wc -l 極速統計(適用超大目錄、百萬級檔案)
 * @param string $dir
 * @param bool   $recursive
 * @return int|false
 */
function countFilesSystem(string $dir, bool $recursive = true): int|false
{
    if (!is_dir($dir)) return false;

    $dir = escapeshellarg($dir);
    $cmd = $recursive
        ? "find $dir -type f -printf '.' | wc -c" // 不輸出換行,直接計字元數,比 wc -l 快
        : "find $dir -maxdepth 1 -type f -printf '.' | wc -c";

    $output = [];
    $returnVar = 0;
    exec($cmd, $output, $returnVar);

    if ($returnVar !== 0) return false;
    return (int)($output[0] ?? 0);
}

// 使用
$total = countFilesSystem('/data/logs', true);
echo "日誌總檔案數: $total";
?>

4. 進階:依副檔名、大小、時間過濾統計

<?php
/**
 * 進階統計:支援多重過濾條件
 */
function countFilesAdvanced(string $dir, array $filters = []): int
{
    $recursive = $filters['recursive'] ?? true;
    $includeHidden = $filters['include_hidden'] ?? false;
    $extensions = isset($filters['extensions']) ? array_map('strtolower', (array)$filters['extensions']) : null;
    $minSize = $filters['min_size'] ?? null; // bytes
    $maxSize = $filters['max_size'] ?? null;
    $modifiedAfter = $filters['modified_after'] ?? null; // timestamp
    $modifiedBefore = $filters['modified_before'] ?? null;

    $iterator = $recursive
        ? new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::LEAVES_ONLY)
        : new DirectoryIterator($dir);

    $count = 0;
    foreach ($iterator as $info) {
        if (!$includeHidden && $info->getFilename()[0] === '.') continue;
        if (!$info->isFile() && !$info->isLink()) continue;

        if ($extensions && !in_array(strtolower($info->getExtension()), $extensions)) continue;
        if ($minSize !== null && $info->getSize() < $minSize) continue;
        if ($maxSize !== null && $info->getSize() > $maxSize) continue;
        if ($modifiedAfter !== null && $info->getMTime() < $modifiedAfter) continue;
        if ($modifiedBefore !== null && $info->getMTime() > $modifiedBefore) continue;

        $count++;
    }
    return $count;
}

// 範例:統計最近 7 天、超過 1MB 的 JPG/PNG 圖片數
$num = countFilesAdvanced('/var/www/uploads', [
    'recursive' => true,
    'extensions' => ['jpg', 'jpeg', 'png'],
    'min_size' => 1024 * 1024,
    'modified_after' => strtotime('-7 days'),
]);
echo "近期大圖片數: $num";
?>

5. 快取策略:避免重複掃描

<?php
function getCachedFileCount(string $dir, int $ttl = 300): int
{
    $cacheKey = 'file_count:' . md5($dir);
    // 假設使用 APCu 或 Redis
    // $cached = apcu_fetch($cacheKey);
    // if ($cached !== false) return $cached;

    $count = countFilesRecursive($dir);
    // apcu_store($cacheKey, $count, $ttl);
    return $count;
}
?>

常見陷阱與解決

  1. 符號連結導致無限迴圈:RecursiveDirectoryIterator 預設不跟隨連結,若啟用 FOLLOW_SYMLINKS 必須自行偵測循環(記錄已訪問 inode)。
  2. 權限不足:遇到無法讀取的子目錄會拋出例外,需 try-catch 包裹迴圈或設定 DirectoryIterator::CATCH_EXCEPTION(非標準,建議自行處理)。
  3. Windows 路徑長度限制:超過 260 字元路徑需啟用長路徑支援(註冊表或 manifest),或縮短目錄結構。
  4. 網路掛載目錄:NFS/SMB 掛載點統計極慢,建議在伺服器端統計後同步結果。

最佳實踐建議

  • 單層、小目錄:DirectoryIterator 最簡潔安全。
  • 遞歸、中等目錄:RecursiveIteratorIterator::LEAVES_ONLY 記憶體穩定、速度快。
  • 超大目錄(10萬+)、Linux 環境:系統指令 find -printf '.' | wc -c 最快,且不佔 PHP 記憶體。
  • 頻繁查詢:加入快取(APCu/Redis),設定合理 TTL 或由寫入端主動失效。
  • 統計資訊持久化:資料庫記錄每日目錄統計,趨勢分析、告警閾值判斷。

選對工具、加上快取,目錄檔案統計不再是效能瓶頸。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved