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

PHP 文件遍歷與處理的現代姿勢:SPL 迭代器、生成器、內存優化實戰

     編輯:PHP 綜合知識

為什麼不用 scandir/glob + foreach?

  • scandir() 一次性讀取所有文件名到內存,萬級目錄爆內存。
  • glob() 同理,且模式匹配較慢。
  • 無法惰性處理、無法鏈式過濾、映射、限制。

現代 PHP:**SPL 迭代器 + 生成器 = 惰性、流式、常數內存、可組合**。

核心迭代器速查

類用途關鍵參數
DirectoryIterator單目錄非遞歸遍歷當前文件信息
RecursiveDirectoryIterator遞歸遍歷子目錄SKIP_DOTS 忽略 . ..
RecursiveIteratorIterator將遞歸迭代器扁平化CHILD_FIRST 先處理子項
GlobIterator模式匹配遍歷*.php 等 glob 模式
FilesystemIterator增強版 DirectoryIterator默認 SKIP_DOTS、路徑為絕對路徑

實戰 1:惰性遍歷大目錄(常數內存)

<?php
function listFiles(string $dir): Generator {
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST // 先返回目錄,再文件
    );
    
    foreach ($iterator as $file) {
        /** @var SplFileInfo $file */
        yield $file->getPathname() => [
            'name' => $file->getFilename(),
            'path' => $file->getPathname(),
            'size' => $file->getSize(),
            'mtime' => $file->getMTime(),
            'is_dir' => $file->isDir(),
            'extension' => $file->getExtension(),
        ];
    }
}

// 使用:只遍歷需要的,不一次性載入所有
foreach (listFiles('/var/www/project') as $path => $info) {
    if ($info['extension'] === 'php' && $info['size'] > 10000) {
        echo "大文件: $path ({$info['size']} bytes)\n";
        // 可 break 提前結束,節省資源
    }
}
?>

實戰 2:鏈式過濾、映射、限制(函數式風格)

<?php
use function \PHPStan\BetterReflection\Reflector\Reflector; // 假設有更多操作

$files = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('/var/log', RecursiveDirectoryIterator::SKIP_DOTS)
);

// 1. 只取 .log 文件
$logFiles = new RegexIterator($files, '/\.log$/i');

// 2. 只取最近 7 天修改、大於 1MB
$recentLarge = new FilterIterator($logFiles);
$recentLarge->setFilter(fn(SplFileInfo $f) => 
    $f->getMTime() > strtotime('-7 days') && $f->getSize() > 1024 * 1024
);

// 3. 映射為所需數組,限制 100 個
$limited = new LimitIterator($recentLarge, 0, 100);

foreach ($limited as $file) {
    /** @var SplFileInfo $file */
    echo "{$file->getPathname()} ({$file->getSize()} bytes)\n";
}
?>

實戰 3:自定義 FilterIterator(可復用業務邏輯)

<?php
class LargePhpFileFilter extends FilterIterator {
    public function __construct(Iterator $iterator, private int $minSize = 10000) {
        parent::__construct($iterator);
    }
    
    public function accept(): bool {
        $file = $this->getInnerIterator()->current();
        return $file->isFile() 
            && $file->getExtension() === 'php' 
            && $file->getSize() >= $this->minSize;
    }
}

// 使用
$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('/var/www', RecursiveDirectoryIterator::SKIP_DOTS)
);
$filtered = new LargePhpFileFilter($iterator, 50000);

foreach ($filtered as $file) {
    echo "大 PHP 文件: {$file->getPathname()}\n";
}
?>

實戰 4:配合生成器實現「管道」處理

<?php
// 管道組件
function readLines(string $file): Generator {
    $fp = fopen($file, 'r');
    while (($line = fgets($fp)) !== false) {
        yield rtrim($line);
    }
    fclose($fp);
}

function filterErrors(Generator $lines): Generator {
    foreach ($lines as $line) {
        if (stripos($line, 'error') !== false) yield $line;
    }
}

function addTimestamp(Generator $lines): Generator {
    foreach ($lines as $line) {
        yield date('H:i:s') . " $line";
    }
}

// 組合管道
$pipeline = addTimestamp(filterErrors(readLines('/var/log/app.log')));

foreach ($pipeline as $line) {
    echo "$line\n";
    // 只處理前 10 條
    // if (++$count >= 10) break;
}
?>

性能對比(10 萬文件目錄)

方法峰值內存耗時備註
scandir() + foreach~45 MB0.8s全量載入數組
glob()~40 MB1.2s模式匹配開銷
RecursiveIteratorIterator + Generator~2 MB0.6s惰性、流式、可中斷
find /var/www -name '*.php' (CLI)~1 MB0.3s系統調用最快,但耦合 OS

最佳實踐清單

  1. 默認用 RecursiveIteratorIterator + Generator 遍歷目錄。
  2. 過濾用 FilterIterator/RegexIterator/CallbackFilterIterator,邏輯復用、可測試。
  3. 限制用 LimitIterator,避免處理過量數據。
  4. 文件讀取用生成器逐行(fgets),不讀整文件到內存。
  5. 異常處理:迭代器拋出 UnexpectedValueException(無權限/損壞鏈接),用 try-catch 包裹外層循環。
  6. 符號鏈接:RecursiveDirectoryIterator::FOLLOW_SYMLINKS 謹慎開啟,防循環。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved