生成器是PHP 5.5引入的革命性特性,允许函数在保持状态的情况下多次返回值。与普通函数不同,生成器函数可以暂停和恢复,每次yield返回一个值。生成器大幅降低了内存占用,使处理大数据集成为可能。
参考:https://xgmoi.cn/category/siji.html
生成器基础:生成器函数使用yield关键字返回值。调用生成器函数返回一个Generator对象(实现了Iterator接口)。遍历Generator对象时,每次迭代执行到下一个yield,返回yield的值。生成器函数可以多次yield,也可以使用return返回最终值(PHP 7+)。
生成器的内存优势巨大。普通函数读取一个大文件时,需要将整个文件加载到内存。生成器逐行读取,每次只保留一行在内存中。处理10GB日志文件时,生成器内存占用可能只有几MB,而普通数组需要10GB内存(通常导致内存耗尽)。
yield的变体:yield $value返回值,键为自动递增整数。yield $key => $value返回键值对。yield不带参数返回null,键自动递增。yield from(PHP 7)委托生成器给另一个可遍历对象(数组、生成器、实现了Traversable的对象)。
yield from的用途:委托生成器可以嵌套生成器。例如,一个生成器yield from anotherGenerator(),外层生成器会自动遍历内层生成器的所有值。yield from也支持数组,简化了扁平化多级列表的操作。
生成器与引用:生成器可以yield引用(yield &$value),允许外部代码修改生成器的内部状态。这在构建数据处理管道时有用,但需要谨慎使用。
生成器与发送数据:生成器不仅可以返回值(yield),还可以接收数据(send)。Generator::send($value)将值发送给生成器,作为当前yield表达式的返回值。这使生成器成为协程的基础。
参考:https://vhjpe.cn/category/mingan-huli.html
生成器作为协程:通过yield和send配合,生成器可以实现简单的协程。生成器在yield处暂停,外部代码决定何时恢复,并可以发送数据。这种模式是ReactPHP、Amp等异步框架的基础。
生成器与异常:Generator::throw(Exception $e)向生成器内部抛出异常,在yield处被捕获。这使外部代码可以将错误信号发送给生成器。
生成器与返回值:PHP 7允许生成器使用return返回最终值。通过Generator::getReturn()获取返回值。这在构建复杂数据处理管道时有用,例如返回统计摘要。
生成器的性能:生成器不是没有开销的。创建Generator对象和每次yield都有微小开销(函数调用、状态保存)。对于小型数据集,普通数组可能更快。生成器的优势在内存而非CPU。
生成器的限制:生成器只能向前迭代,不能后退或重置(除非重新创建)。生成器不能通过下标访问,也不能使用count(除非手动收集到数组)。生成器的递归需要小心,因为每次递归调用都会创建新的Generator对象。
参考:https://qeext.cn/category/guide.html
实际应用:大数据文件处理:逐行处理CSV文件,过滤、转换、聚合。生成器管道可以串联多个处理步骤,每个步骤是一个生成器,使代码模块化且内存友好。
实际应用:数据库分块处理:使用生成器分批查询数据库,yield每批结果。避免一次性加载百万行记录到内存,同时保持迭代器的简洁性。
实际应用:无限数据流:生成器可以产生无限序列,如斐波那契数列、随机数流。外部代码决定取多少个元素(通过循环计数或LimitIterator)。
实际应用:递归遍历目录:使用生成器递归遍历文件系统,每次yield一个文件路径。相比递归函数返回所有路径的数组,生成器可以即时处理发现的文件。
生成器与迭代器的比较:实现Iterator接口的类也可以实现惰性求值,但代码更冗长。生成器用极少的代码实现了相同的功能。对于复杂的迭代逻辑,实现Iterator类可能更清晰。
PHP 8的生成器改进:PHP 8允许在yield中使用表达式(如yield $foo = $bar)。Generator类添加了getTrace和getTraceAsString方法,用于调试生成器内部的执行流程。
生成器的学习曲线较陡,但掌握后可以大大提升代码质量和性能。对于任何可能消耗大量内存的迭代操作,首先考虑生成器。生成器也是迈向PHP异步编程的第一步。
参考:https://xgmoi.cn