PHP生成器yield
PHP开源社区 人气:0如果是做Python或者其他语言的小伙伴,对于生成器应该不陌生。但很多PHP开发者或许都不知道生成器这个功能,可能是因为生成器是PHP 5.5.0才引入的功能,也可以是生成器作用不是很明显。但是,生成器功能的确非常有用。
1. 什么是 "yield"
生成器函数看上去就像一个普通函数, 除了不是返回一个值之外, 生成器会根据需求产生更多的值。
看以下的例子:
function getValues() { yield 'value'; } // 输出字符串 "value" echo getValues();
当然, 这不是他生效的方式, 前面的例子会给你一个致命的错误: 类生成器的对象不能被转换成字符串
2.yield 解决的问题
解决运行内存的瓶颈,php程序中的变量存储在内存中,之前有遇到过读取Excel文件时候,会出现内存不足,出现:
Fatal Error: Allowed memory size of xxxxxx bytes
所以会设置php 最大运行内存的设置: ini_set('memory_limit', '200M')
但是当我们读取5g 这么大的文件的时候,我们运行内存可能就吃不消了,所以会选择yield
3."yield" & "return" 的区别
前面的错误意味着 getValues()
方法不会如预期返回一个字符串,让我们检查一下他的类型:
function getValues() { return 'value'; } var_dump(getValues()); // string(5) "value" function getValues() { yield 'value'; } var_dump(getValues()); // class Generator#1 (0) {}
生成器 类实现了 生成器 接口, 这意味着你必须遍历 getValue()
方法来取值:
foreach (getValues() as $value) { echo $value; } // 使用变量也是好的 $values = getValues(); foreach ($values as $value) { echo $value; }
但这不是唯一的不同!
一个生成器运行你写使用循环来迭代一维数组的代码,而不需要在内存中创建是一个数组,这可能会导致你超出内存限制。
在下面的例子里我们创建一个有 800,000 元素的数字同时从 getValues()
方法中返回他,同时在此期间,我们将使用函数 memory_get_usage() 来获取分配给次脚本的内存, 我们将会每增加 200,000 个元素来获取一下内存使用量,这意味着我们将会提出四个检查点:
<?php function getValues() { $valuesArray = []; // 获取初始内存使用量 echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL; for ($i = 1; $i < 800000; $i++) { $valuesArray[] = $i; // 为了让我们能进行分析,所以我们测量一下内存使用量 if (($i % 200000) == 0) { // 来 MB 为单位获取内存使用量 echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB'. PHP_EOL; } } return $valuesArray; } $myValues = getValues(); // 一旦我们调用函数将会在这里创建数组 foreach ($myValues as $value) {}
前面例子发生的情况是这个脚本的内存消耗和输出:
0.34 MB
8.35 MB
16.35 MB
32.35 MB
这意味着我们的几行脚本消耗了超过 30 MB
的内存, 每次你你添加一个元素到 $valuesArray
数组中, 都会增加他在内存中的大小。
让我们使用 yield 同样的例子:
<?php function getValues() { // 获取内存使用数据 echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL; for ($i = 1; $i < 800000; $i++) { yield $i; // 做性能分析,因此可测量内存使用率 if (($i % 200000) == 0) { // 内存使用以 MB 为单位 echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB'. PHP_EOL; } } } $myValues = getValues(); // 在循环之前都不会有动作 foreach ($myValues as $value) {} // 开始生成数据
这个脚本的输出令人惊讶:
0.34 MB
0.34 MB
0.34 MB
0.34 MB
这不意味着你从 return 表达式迁移到 yield,但如果你在应用中创建会导致服务器上内存出问题的巨大数组,则 yield 更加适合你的情况。
4. 什么是 "yield" 选项
这里有很多 yield 的选项, 我将强调他们中的几个:
a. 使用 yield, 你也可以使用 return。
function getValues() { yield 'value'; return 'returnValue'; } $values = getValues(); foreach ($values as $value) {} echo $values->getReturn(); // 'returnValue'
b. 返回键值对:
function getValues() { yield 'key' => 'value'; } $values = getValues(); foreach ($values as $key => $value) { echo $key . ' => ' . $value; }
5. 生成器
1 使用生成器来生成一个1到100的数组
function my_range($start,$limit){ for($i=$start;$i<=$limit;$i++){ yield $i; } }
2 打印出来,看下返回究竟是什么
$arr = my_range(1,100); var_dump($arr);
结果是:
object(Generator)#1 (0) { }
可见是一个对象,是一个生成器对象,既然是对象那么也就是可以用foreach来遍历
3 遍历生成器
foreach($arr as $num){ echo $num.PHP_EOL; }
看到可以完整遍历出来,那么与那样实现的不同地方,意义在哪里呢。重点来了。
4 两者内存占用比较
上面已经测试过使用数组的方式,随着范围的增大占用的内存剧增,很快就超过了PHP的内存上限。
那么使用生成器占用了多少内存呢,来看看就知道了。
$start = memory_get_usage(); $arr = my_range(1, 100); $end = memory_get_usage(); echo $end - $start .' bytes'.PHP_EOL;
可以看到只占用了576bytes,当然每个人测试的可能都会有点不同,环境不同,但是这不是重点。
我们再尝试增加数字范围,可以看到数字范围并没有影响到内存占用,也就是可以轻松的遍历超大数字。
$start = memory_get_usage(); $arr = my_range(1, 100000000); $end = memory_get_usage(); echo $end - $start .' bytes'.PHP_EOL; foreach($arr as $num){ echo $num.PHP_EOL; }
这下我们就可以遍历1到10000000的数字了,不相信内存占用那么低的小伙伴,可以打开任务管理器毫无波澜,即时再上调数字范围。
5、生成器遍历原理
生成器既然这么强大,那么他的遍历原理是什么呢。使用foreach遍历的时候,相当于生成器执行了以下操作。
while($arr->valid()){ echo $arr->current().PHP_EOL; $arr->next(); } //$arr->valid() 判断生成器是否关闭 //$arr->current() 返回当前对象 //$arr->next() 继续往下执行生成器
加载全部内容