今天在闲逛PHP的官方文档的时候,发现了这么个用法:yield
,叫 生成器
官方是这么描述的
(PHP 5 >= 5.5.0, PHP 7)
生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低。
生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。
一个简单的例子就是使用生成器来重新实现 range() 函数。 标准的 range() 函数需要在内存中生成一个数组包含每一个在它范围内的值,然后返回该数组, 结果就是会产生多个很大的数组。 比如,调用 range(0, 1000000) 将导致内存占用超过 100 MB。
做为一种替代方法, 我们可以实现一个 xrange() 生成器, 只需要足够的内存来创建 Iterator 对象并在内部跟踪生成器的当前状态,这样只需要不到1K字节的内存。
果然还是完全看不懂在说什么。看下官方的示例代码吧:
/*
* for the protection from the leaking of resources
* see RFC https://wiki.php.net/rfc/generators#closing_a_generator
* and use finnaly
*/
//sample code
function getLines($file) {
$f = fopen($file, 'r');
try {
while ($line = fgets($f)) {
yield $line;
}
} finally {
fclose($f);
}
}
foreach (getLines("file.txt") as $n => $line) {
if ($n > 5) break;
echo $line;
}
简而言之呢,就是如果在某个函数中使用了生成器yield
那么这个这个函数会被推迟到实际被foreach
语句迭代的时候才会被调用。
官方的描述是会大量节省内存,提高内存。我心想这是神器啊!但是转念一想,如果真的这么好用,为啥却没有被大规模使用呢?是出反常必有妖。
下面测试一下:
传统写法如下:
function test()
{
$data = [];
for ($i = 0; $i < 1000000; $i++) {
//echo "执行了一次";
$data[] = $i;
}
}
foreach (test() as $value) {
var_dump($value);
//echo "<br>";
}
然后看运行时间和内存占用是:
- 运行时间为:0.0513
- 消耗内存为:0.7968KB
使用生成器的写法:
function test()
{
for ($i = 0; $i < 1000000; $i++) {
//echo "执行了一次";
yield $i;
}
}
foreach (test() as $value) {
var_dump($value);
//echo "<br>";
}
再看下运行的时间与消耗的内存:
- 运行时间为:0.5249
- 消耗内存为:0.1093KB
emmmm… 传统写法因为会一次性先把数据存入内存,所以占用内存比较大。而yield的写法,则可以有效节约内存。但是相对应的代价就是:时间消耗大约是传统写法的十倍
看来yield
只适合处理较大数据例如数G的表格之类的,明显对内存造成了过大的压力情况下才去使用。在数据量比较小的情况下,还是不要使用了。至于背后的原理,则不是本身讨论的内容了(我不会说是因为我也不知道)。