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 协议》,转载必须注明作者和本文链接
阿修罗
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 8

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

1周前 评论
summerbloom (楼主) 1周前
陈先生
1周前 评论
summerbloom (楼主) 1周前

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

1周前 评论
summerbloom (楼主) 1周前

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

5天前 评论

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

4天前 评论

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