你应该知道的 PHP 生成器知识 ( 上 )
回顾下我们前一章节 鲜为人知的 PHP range() 函数 心心念念的 range()
函数,这个超好用的 range()
函数也不是没有缺点,因为它会一次性生成我们需要的范围系列。
如果我们在某个地方生成了一个超大的数组系列,比如 range(0,10000000)
,但有时候可能只会用到其中十分之一,那就得不偿失了。因为生成的超大序列也需要消耗时间,那剩下的十分之九不就白白浪费了。
$nums = range(0,100000000); foreach($nums as $v) { echo $v,"\n"; // 模拟只使用十分之一 if ( range(0,9) == 9 ) { break; } }
再如,我们前面提到的 PHP 对象迭代。实现一个对象迭代,我们要实现 Iterator 接口,还需要实现 5 个方法,这种方式看起来还是比较笨重的,性能开销和复杂性都很大。
我一直再想,要是有一种方式,每次调用一次函数返回一个值,这个函数还可以记住每次调用的状态(位置),保证下次调用的时候能够延续上一次调用,比如下面这个我们重写的 xrange()
函数
function xrange($start, $limit, $step = 1) { if ($start < $limit) { if ($step <= 0) { throw new LogicException('Step must be +ve'); } for ($i = $start; $i <= $limit; $i += $step) { yield $i; // 先返回 $i; 然后暂停执行 } } else { if ($step >= 0) { throw new LogicException('Step must be -ve'); } for ($i = $start; $i >= $limit; $i += $step) { yield $i; // 先返回 $i; 然后暂停执行 } } }
我们希望每次运行到 yield
这条语句的时候先返回当前值 $i
,接着函数暂时执行,记住刚刚执行的位置,然后等待下一次函数调用则直接从 yield 后一条语句开始执行。
在这种执行机制下,我们就实现了按需生成。
在所有的主流语言中,这种按需生成的方式叫做 生成器 ( Generator )
PHP 中的生成器 ( Generator )
PHP 在 5.5.0 中引入了生成器这个概念。不过坑爹的是,引入的初衷竟然是简化对象迭代。但有总没有好,希望 PHP 会慢慢优化吧。毕竟生成器是当下语言的发展趋势。
生成器提供了一种迭代对象和数组的新方式,比如迭代数组,允许我们使用 foreach 来迭代一组数据而不需要事先在内存中创建一个数组, 那会使我们的内存达到上限,或者会占据客观的处理时间。
相反,我们可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield
多次,以便生成需要迭代的值。
一个简单的例子就是上面我们提到的 xrange()
函数。PHP 内置的函数 range()
函数需要在内存中生成一个数组包含每一个在它范围内的值,然后返回该数组, 结果就是会产生多个很大的数组。 比如,调用 range(0, 1000000)
将导致内存占用超过 100 MB。
做为一种替代方法, 就如同上面我们刚刚实现的 xrange()
生成器, 只需要足够的内存来创建 Iterator 对象并在内部跟踪生成器的当前状态,这样只需要不到 1K 字节的内存。
我们写一个示例演示下刚刚的 xrange()
函数
<?php function xrange($start, $limit, $step = 1) { if ($start < $limit) { if ($step <= 0) { throw new LogicException('Step must be +ve'); } for ($i = $start; $i <= $limit; $i += $step) { yield $i; } } else { if ($step >= 0) { throw new LogicException('Step must be -ve'); } for ($i = $start; $i >= $limit; $i += $step) { yield $i; } } } /* * 注意下面range()和xrange()输出的结果是一样的。 */ echo 'Single digit odd numbers from range(): '; foreach (range(1, 9, 2) as $number) { echo "$number "; } echo "\n"; echo 'Single digit odd numbers from xrange(): '; foreach (xrange(1, 9, 2) as $number) { echo "$number "; } ?>
运行这段代码,输出结果为
Single digit odd numbers from range(): 1 3 5 7 9 Single digit odd numbers from xrange(): 1 3 5 7 9
对象生成器
当第一次调用生成器函数时,将返回内部 生成器 ( Generator ) 类的对象。此对象实现 Iterator 接口的方式与只向前 ( forward-only ) 迭代器对象的方式非常相似,并提供可以调用以操纵生成器状态的方法,包括向其发送值和从中返回值。