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 协议》,转载必须注明作者和本文链接
推荐文章: