統計資料夾檔案數量是常見需求(上傳限額、備份監控、清理策略)。PHP 提供多種方式,各有效能與功能差異。本文比較 scandir、glob、DirectoryIterator、RecursiveDirectoryIterator、系統指令,並提供生產環境可用的封裝函數。
| 方法 | 遞歸 | 記憶體 | 速度 | 特點 |
|---|---|---|---|---|
scandir() | 否 | 高(全載入陣列) | 快 | 簡單,需自行過濾 . 和 .. |
glob() | 否 | 高 | 中 | 支援模式匹配(*.jpg),大量檔案較慢 |
DirectoryIterator | 否 | 低(逐一迭代) | 快 | SPL 物件導向,可直接判斷型別、大小、時間 |
RecursiveDirectoryIterator | 是 | 低 | 中 | 遞歸遍歷整棵目錄樹 |
exec('find ...') | 是 | 低 | 最快 | Linux/Unix 專用,需 exec 權限,路徑需跳脫 |
<?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";
?>
<?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";
?>
<?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";
?>
<?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";
?>
<?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;
}
?>
RecursiveDirectoryIterator 預設不跟隨連結,若啟用 FOLLOW_SYMLINKS 必須自行偵測循環(記錄已訪問 inode)。try-catch 包裹迴圈或設定 DirectoryIterator::CATCH_EXCEPTION(非標準,建議自行處理)。DirectoryIterator 最簡潔安全。RecursiveIteratorIterator::LEAVES_ONLY 記憶體穩定、速度快。find -printf '.' | wc -c 最快,且不佔 PHP 記憶體。選對工具、加上快取,目錄檔案統計不再是效能瓶頸。