PHP使用yield 读取超大型目录、大目录的方法

之前碰到一个问题,需要处理一个超大型目录,目录有多大呢,单目录有200G大小,大部分人的思路如下, 用日常的递归,基本上读取到的路径数组非常大,会导致超出内存,特此研究了一番:

一般常见的方法如下:

function recursiveScan($dir) {
    $files = [];
    $items = scandir($dir);

    foreach ($items as $item) {
        if ($item == '.' || $item == '..') {
            continue;
        }

        $path = $dir . '/' . $item;

        if (is_dir($path)) {
            $files = array_merge($files, recursiveScan($path)); // 递归读取子目录
        } else {
            $files[] = $path;
        }
    }

    return $files;
}

$directory = '/path/to/directory';
$files = recursiveScan($directory);

foreach ($files as $file) {
    echo $file . PHP_EOL;
}

上面的方法是很常见的做法,读取小文件夹没问题,但是读取大型乃至超大型文件夹会超出内存,通常出现如下提示:

PHP Fatal error:  Allowed memory size of 536870912 bytes exhausted (tried to allocate 134217736 bytes) 

解决思路:
1、我们将 recursiveScan 函数改为生成器函数,并使用 yield $path; 逐步生成文件路径。这样在调用这个函数时,可以逐步处理文件而不是一次性返回所有文件路径。这种方式可以减少内存占用,并在需要时逐个获取文件路径。
2、不使用递归这种耗资源的方式, 使用array_pop 栈的思路来模拟迭代

改进过的方法如下,非常的节省内存,默认128m的配置即可读取超大目录:

function recursiveScan($folder) {
    $stack = [$folder]; // 使用栈来模拟迭代

    $ext_map = $this->config('ext_map');
    $ext_map_arr = explode("\n", $ext_map);

    while (!empty($stack)) {
        $currentFolder = array_pop($stack);
        $handle = opendir($currentFolder);

        while (($file = readdir($handle)) !== false) {
            if ($file != '.' && $file != '..') {
                $path = $currentFolder . '/' . $file;

                if (is_dir($path)) {
                    $stack[] = $path; //判断目录则入栈,代替迭代
                } else {

                    yield $path; // 生成器函数逐步生成文件路径
                }
            }
        }

        closedir($handle);
    }
}

使用的话,就直接用foreach循环上面的结果, 返回$path即可,跟普通的思路一样

foreach(recursiveScan($folder) as $path){
    echo $path.PHP_EOL;
}

这样就可以轻松读取200G甚至2T的文件夹了,是不是很简单了?

本作品采用《CC 协议》,转载必须注明作者和本文链接
阿修罗
本帖由系统于 1个月前 自动加精
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 14

看着就很厉害的样子,不过我并没有看懂,先Mark一下,万一日后有需求呢。

2个月前 评论
summerbloom (楼主) 2个月前
2个月前 评论
summerbloom (楼主) 2个月前

:full_moon_with_face: 其实在某种程度上,即使不存在yeild也可以正常运行,就比如,你把yeild 换成一个callback函数,你的代码也是可以正常无压力运行的,yeild更多的是实现了foreach 这种形式的写法

2个月前 评论
summerbloom (楼主) 2个月前

读取目录扔队列里简单点吧

2个月前 评论
summerbloom (楼主) 1个月前
mshx (作者) 1个月前

真实业务场景也不需要一次性读取所有目录及子目录。一般都交互操作操作到哪个目录再继续下一个了。

2个月前 评论
summerbloom (楼主) 1个月前

胖友 你听说过SPL库嘛?

1个月前 评论
summerbloom (楼主) 1个月前
gowithwind (作者) 1个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!