生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低。
生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。
案例:遍历大数组
下面我们实现一个简单的函数,用于生成一个范围内的数值,用来说明PHP生成器是如何节省内存的。
<?php function makeRange(int $length): array { $dataSet = []; for ($i = 0; $i < $length; $i++) { $dataSet[] = $i; } return $dataSet; } $customerRange = makeRange(1000000); foreach ($customerRange as $i) { echo $i . PHP_EOL; } echo 'memory usage:' . memory_get_peak_usage() / 1024 / 1024 . PHP_EOL;
上图中的代码没有善用内存,因为makeRange()函数要为预先创建的由一百万个整数组成的数组分配内存。我们可以看到大概分了32MB。PHP生成器能实现相同的操作,不过一次只会为一个整数分配内存。那我们换成生成器的方式来看下
<?php
function makeRange(int $length)
{
for ($i = 0; $i < $length; $i++) {
yield $i;
}
}
foreach (makeRange(1000000) as $i) {
echo $i . PHP_EOL;
}
echo 'memory usage:' . memory_get_peak_usage() / 1024 / 1024 . PHP_EOL;
两者一对比的话,相差就很大了,不过现实工作中我们很少这样使用生成随机数方法,上面的话只是为了对比差异。
案例:导入csv文件
项目中能想到的例子就是导入csv文件假如我们想迭代一个4GB的CSV文件,但服务器只允许PHP使用2GB内存,因此不能把整个文件都加载到内存中,下面展示了如何使用生成器来完成这种操作。
<?php
function getRows($file)
{
$handle = fopen($file, 'rb');
if ($handle === false) {
throw new \Exception();
}
while (feof($handle) === false) {
yield fgetcsv($handle);
}
fclose($handle);
}
foreach (getRows('data.csv') as $row) {
print_r($row);
}
上述示例只会为CSV文件中的一行分配内存,而不会把整个4GB的CSV文件都读取到内存中。生成器没有为PHP添加新功能,不用生成器也能做很多生成器能做的事情。不过,生成器大大简化了某些任务,而且使用的内存更少。
参考资料:
https://www.php.net/manual/zh/language.generators.syntax.php
https://blog.ircmaxell.com/2012/07/what-generators-can-do-for-you.html
https://www.php.net/manual/zh/function.memory-get-peak-usage
《Modern PHP》