如何通过yield关键字在PHP中高效处理大数组,减少内存消耗?
- 内容介绍
- 文章标签
- 相关推荐
本文共计995个文字,预计阅读时间需要4分钟。
如果PHP中遍历包含数十万至百万元素的大数组时频繁遭遇内存溢出或响应缓慢,很可能是因为传统方式将全部数据一次性加载进内存所致。以下是使用yield关键字优化大数组遍历的示例:
一、重构数据生成逻辑为生成器函数
该方法通过将原本返回完整数组的函数改为生成器函数,使数据按需产出而非预加载,从而将内存占用从线性增长压缩至恒定级别。
1、将原函数中使用 return $array 的语句全部移除。
2、在循环体内,将原本向数组追加元素的代码(如 $arr[] = $item)替换为 yield $item。
立即学习“PHP免费学习笔记(深入)”;
3、确保函数声明返回类型为 Generator,例如 function getData(): Generator。
4、调用该函数时不再接收数组,而是直接用于 foreach 循环消费。
二、使用xrange类生成器替代range函数
当需要遍历数值范围时,range() 会立即构建完整整数数组,而自定义xrange生成器仅保存起始、结束和步长状态,每次迭代只计算一个值。
1、定义函数 function xrange($start, $end, $step = 1): Generator。
2、在函数体内使用 for ($i = $start; $i 循环结构。
3、循环内每轮执行 yield $i,不声明任何中间数组变量。
4、在业务代码中将原 foreach (range(1, 1000000) as $n) 替换为 foreach (xrange(1, 1000000) as $n)。
三、封装数据库查询为逐行生成器
针对PDO或MySQLi查询结果,避免使用 fetchAll() 加载全部记录,改用游标式单行获取并yield,使每行数据处理完毕后即被释放。
1、使用 $stmt->execute() 执行查询后,不调用 fetchAll()。
2、进入 while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) 循环。
3、循环体内直接执行 yield $row,不累积到数组。
4、外部消费时用 foreach (getUsersGenerator() as $user) 处理单行数据。
四、处理大文件时逐行yield文本行
读取GB级日志或CSV文件时,若用 file() 或 file_get_contents() 会触发内存爆炸;改用fopen + fgets配合yield,可将内存稳定控制在KB级。
1、在生成器函数内调用 $handle = fopen($filename, 'r') 打开文件。
2、使用 while (($line = fgets($handle)) !== false) 逐行读取。
3、每读取一行即执行 yield trim($line),不缓存历史行内容。
4、处理完成后在生成器末尾调用 fclose($handle)。
五、递归结构展开时用yield替代递归数组拼接
遍历深度未知的树形结构(如无限级分类、嵌套JSON)时,传统递归易导致栈溢出与内存堆积;yield可将深度优先遍历转为惰性流式产出。
1、生成器函数接收节点参数,检查其是否含子节点。
2、先 yield $node 当前节点,再对每个子节点递归调用自身。
3、递归调用处必须使用 yield from traverseTree($child),而非 array_merge() 或赋值拼接。
4、确保所有分支路径最终都以 yield 或 yield from 终止,不返回数组。
本文共计995个文字,预计阅读时间需要4分钟。
如果PHP中遍历包含数十万至百万元素的大数组时频繁遭遇内存溢出或响应缓慢,很可能是因为传统方式将全部数据一次性加载进内存所致。以下是使用yield关键字优化大数组遍历的示例:
一、重构数据生成逻辑为生成器函数
该方法通过将原本返回完整数组的函数改为生成器函数,使数据按需产出而非预加载,从而将内存占用从线性增长压缩至恒定级别。
1、将原函数中使用 return $array 的语句全部移除。
2、在循环体内,将原本向数组追加元素的代码(如 $arr[] = $item)替换为 yield $item。
立即学习“PHP免费学习笔记(深入)”;
3、确保函数声明返回类型为 Generator,例如 function getData(): Generator。
4、调用该函数时不再接收数组,而是直接用于 foreach 循环消费。
二、使用xrange类生成器替代range函数
当需要遍历数值范围时,range() 会立即构建完整整数数组,而自定义xrange生成器仅保存起始、结束和步长状态,每次迭代只计算一个值。
1、定义函数 function xrange($start, $end, $step = 1): Generator。
2、在函数体内使用 for ($i = $start; $i 循环结构。
3、循环内每轮执行 yield $i,不声明任何中间数组变量。
4、在业务代码中将原 foreach (range(1, 1000000) as $n) 替换为 foreach (xrange(1, 1000000) as $n)。
三、封装数据库查询为逐行生成器
针对PDO或MySQLi查询结果,避免使用 fetchAll() 加载全部记录,改用游标式单行获取并yield,使每行数据处理完毕后即被释放。
1、使用 $stmt->execute() 执行查询后,不调用 fetchAll()。
2、进入 while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) 循环。
3、循环体内直接执行 yield $row,不累积到数组。
4、外部消费时用 foreach (getUsersGenerator() as $user) 处理单行数据。
四、处理大文件时逐行yield文本行
读取GB级日志或CSV文件时,若用 file() 或 file_get_contents() 会触发内存爆炸;改用fopen + fgets配合yield,可将内存稳定控制在KB级。
1、在生成器函数内调用 $handle = fopen($filename, 'r') 打开文件。
2、使用 while (($line = fgets($handle)) !== false) 逐行读取。
3、每读取一行即执行 yield trim($line),不缓存历史行内容。
4、处理完成后在生成器末尾调用 fclose($handle)。
五、递归结构展开时用yield替代递归数组拼接
遍历深度未知的树形结构(如无限级分类、嵌套JSON)时,传统递归易导致栈溢出与内存堆积;yield可将深度优先遍历转为惰性流式产出。
1、生成器函数接收节点参数,检查其是否含子节点。
2、先 yield $node 当前节点,再对每个子节点递归调用自身。
3、递归调用处必须使用 yield from traverseTree($child),而非 array_merge() 或赋值拼接。
4、确保所有分支路径最终都以 yield 或 yield from 终止,不返回数组。

