一、yield介绍
文档介绍说道:生成器函数的核心是yield关键字。它最简单的调用形式看起来像一个return申明,不同之处在于普通return会返回值并终止函数的执行,而yield会返回一个值给循环调用此生成器的代码并且只是暂停执行生成器函数。
说了这么多到底是个东西呢,只有自己亲自动手写代码才知道。
二、range函数探索
1、首先看下我们常用的一个产生数组的函数range()
$ran = range(1, 100);
var_dump($ran)//可以看到是一个产生了一个数组
//既然是数组就来遍历看下效果吧
foreach (range(1, 100) as $num) {
echo $num.PHP_EOL;
}
2、现在我们自己来实现这个range()函数
function my_range($start,$limit){
$arr = [];
for($i=$start;$i<=$limit;$i++){
$arr[] =$i;
}
return $arr;
}
了解了这个函数构造后,发现并没有什么神奇的地方,那生成器和这个有什么关系呢,继续往下看。
三、内存占用
1、当我们遍历的范围很小的时候还看不到什么问题,当我们要遍历的数字很大就会超过PHP的内存限制,这下使用生成器就非常有必要了。
2、我们先看上面的range(1, 100)产生的数组占用了多少内存
$start = memory_get_usage();
echo $start.' bytes'.PHP_EOL;
$arr = range(1, 100);
$end = memory_get_usage();
echo $end - $start .' bytes'.PHP_EOL;
结果为:8280 bytes
3、试着增大数字的范围可以看到占用的内存越来越大,下面是我的一组在CLI模式测试的占用内存情况
// 10 728 bytes
// 100 8280 bytes
// 1000 36952 bytes
// 10000 528472 bytes
// 100000 6291568 bytes
// 1000000 35651696 bytes
如果继续增加到10000000,就会报错。根据个人设置PHP内存上限而定。我设置的是512M。
Fatal error: Allowed memory size of 536870912 bytes exhausted (tried to allocate 536870920 bytes) in C:wamp64wwwphpdemoyield.php on line 31
4、如果我们就是要操作很大的数字遍历,除了调整内存限制,就无计可施了嘛,这下我们的生成器就要隆重登场了。
四、生成器
1、上面我们自己实现了range()方法,这次我们重新使用生成器来实现这个方法
function my_range($start,$limit){
for($i=$start;$i<=$limit;$i++){
yield $i;
}
}
2、打印出来,看下返回究竟是什么
$arr = my_range(1,100);
var_dump($arr);
- 1
- 2
结果是:
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() 继续往下执行生成器
五、结后语
你以为生成器就只有这些用处吗,当然不止。可以使用在很多大量数据的获取场景中,一次性从数据库读取超多的数据,一次性从文本读取超多行文本,都可以这样处理。
不仅用于解决内存问题,还有其他的用武之地,只因本人才疏学浅只能先探索这么多了。