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

PHP遞歸遍歷目錄:獲取所有文件與子目錄完整路徑

     編輯:PHP 相關知識

PHP 目錄遞歸掃描實現與優化指南

文件系統遍歷是批量處理文件、構建文件索引、清理臨時文件的基礎操作。本文提供標準遞歸寫法、SPL 迭代器現代寫法、以及大目錄下的性能優化建議。

方案一:經典遞歸函數 (兼容 PHP 4+)

<?php
/**
 * 遞歸遍歷目錄,返回所有文件完整路徑數組
 * @param string $dir 起始目錄絕對路徑
 * @param array $results 引用傳遞結果數組
 * @return array
 */
function scanDirRecursive($dir, &$results = []) {
    // 規範化路徑
    $dir = rtrim($dir, '/\\');
    
    if (!is_dir($dir) || !is_readable($dir)) {
        trigger_error("目錄不可讀或不存在: $dir", E_USER_WARNING);
        return $results;
    }

    $handle = opendir($dir);
    if (!$handle) return $results;

    while (($entry = readdir($handle)) !== false) {
        // 跳過 . 和 ..
        if ($entry === '.' || $entry === '..') continue;

        $path = $dir . DIRECTORY_SEPARATOR . $entry;

        if (is_file($path)) {
            $results[] = $path;
        } elseif (is_dir($path)) {
            // 遞歸進入子目錄
            scanDirRecursive($path, $results);
        }
        // 注意:is_link() 情況下需決定是否跟隨鏈接,避免循環
    }
    closedir($handle);
    return $results;
}

// 使用示例
$allFiles = scanDirRecursive('/var/www/html/uploads');
foreach ($allFiles as $file) {
    echo $file . PHP_EOL;
}
?>

方案二:SPL RecursiveDirectoryIterator (PHP 5+ 推薦,面向對象、功能豐富)

<?php
$dir = '/var/www/html/uploads';

$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($dir, 
        RecursiveDirectoryIterator::SKIP_DOTS // 自動跳過 . 和 ..
        | RecursiveDirectoryIterator::UNIX_PATHS // 路徑分隔符統一為 /
    ),
    RecursiveIteratorIterator::SELF_FIRST // 先目錄後文件,或 CHILD_FIRST
);

// 過濾器:只獲取文件,排除隱藏文件、特定擴展名
$filter = new RegexIterator($iterator, '/^.+\.(php|js|css|html?)$/i', RegexIterator::MATCH);

foreach ($filter as $fileInfo) {
    /** @var SplFileInfo $fileInfo */
    echo "文件: {$fileInfo->getPathname()}\n";
    echo "大小: {$fileInfo->getSize()} bytes\n";
    echo "修改時間: " . $fileInfo->getMTime() . "\n";
    echo "權限: " . substr(sprintf('%o', $fileInfo->getPerms()), -4) . "\n";
    echo "---\n";
}

// 統計信息
echo "總文件數: " . iterator_count($filter) . "\n";
?>

SPL 優勢:內置跳過 ./..、支持正則過濾、可獲取 SplFileInfo 完整元數據、可控制遍歷順序、內存效率高 (迭代器模式)。

方案三:Glob 簡單模式匹配 (非遞歸,需配合循環)

<?php
// 只掃描一級目錄下的 .php 文件
$files = glob('/var/www/html/*.php');
// 遞歸模式需 PHP 5.5+ GLOB_BRACE 不支持遞歸,需自行實現
?>

大目錄性能優化與避坑指南

問題原因解決方案
遞歸深度過大導致棧溢出PHP 默認棧限制、極深目錄結構改用 SPL 迭代器 (內部用棧模擬) 或增加 ini_set('xdebug.max_nesting_level', 500)
掃描超時 (max_execution_time)文件數十萬級,單次請求超時CLI 模式運行、分批處理、設置 set_time_limit(0)、生成緩存索引文件
內存耗盡結果數組過大全部載入內存使用 Generator (yield) 逐個產出、或直接處理不存數組、寫入臨時文件/數據庫
符號鏈接循環软链接指向父目錄記錄已訪問 inode ($fileInfo->getInode()),遇重複跳過
權限拒絕警告無讀取權限的子目錄@opendir 抑制錯誤、is_readable() 預判、錯誤日誌記錄而非中斷

Generator 版本 (內存友好,適合大目錄)

<?php
function scanDirGenerator($dir) {
    $dir = rtrim($dir, '/\\');
    $handle = @opendir($dir);
    if (!$handle) return;

    while (($entry = readdir($handle)) !== false) {
        if ($entry === '.' || $entry === '..') continue;
        $path = $dir . DIRECTORY_SEPARATOR . $entry;
        if (is_file($path)) {
            yield $path;
        } elseif (is_dir($path)) {
            yield from scanDirGenerator($path); // PHP 7+ 委托生成器
        }
    }
    closedir($handle);
}

// 使用:逐個處理,內存常數級
foreach (scanDirGenerator('/data/logs') as $file) {
    if (filesize($file) > 100 * 1024 * 1024) {
        echo "大文件: $file\n";
    }
}
?>

總結選型

  • 簡單腳本、小目錄:經典遞歸函數最直觀。
  • 項目代碼、需元數據、過濾:SPL RecursiveDirectoryIterator 首選。
  • 超大目錄、長時間運行:CLI + Generator + 分批 + 緩存索引。
  • 高性能掃描:考慮 fscandir (PECL) 或系統命令 find /path -type f 配合 proc_open。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved