PHP中的生成器

描述

你好, 我是程序猿零壹。******

******今天要给大家带来php中yield的用法。对于yield,我相信大部分的人依旧是不会用,甚至不知道什么是yield。那么今天这篇文章就是来告诉大家有关于yield的一些用法,以及如何用yield来解决我们在php中遇到的问题。希望对大家有所帮助。******

******不知道大家有没有碰到过这样的问题,使用excel导入大量数据的时候会失败,并且提示以下错误信息:******

Fatal Error: Allowed memory size of xxxxxx bytes

******这个是因为在php程序中,是将变量存储在内存中。当从excel中要导入的数据量过大的时候,会出现内存不足的错误提示。

要解决这个问题,可以通过修改php中对于最大运行内存的设置:

ini_set('memory_limit', '200M');

但是这么做不能从根本上解决问题,当我们需要读取5g甚至更大文件的时候,我们的运行内存可能就吃不消了。

幸好,在php5.5之后提新增了生成器(Generators)特性,用于简化实现迭代器接口(iterator)创建简单的迭代器的复杂性。通过生成器,我们可以轻松的使用 foreach 迭代一系列的数据,而不需要事先在内存中构建要被迭代的对象,大大减少了内存的开销。

这样说可能比较抽象,不易于理解。所以我们先抛开有关于生成器的概念,先来看一个简单的例子。

$arr = range(1,100)

这里用到了range函数,它的作用是在内存中生成一个数组包含每个在指定范围内的值,并返回该数组。

如果我们自己来实现这样一个数组,应该怎么做呢?我们来看下面的代码:

function xrange($start,$end,$step=1){  
  $data = [];  
  for($i=$start;$i<$end,$i += $step) {    
    $data[] = $i;  
  }  
  return $data;
}
$start = memory_get_usage();
$data = xrange(1,1000);
foreach ($data as $value) {    
  echo $value.PHP_EOL;
}
$end = memory_get_usage();
echo "start:".$start.PHP_EOL;
echo "end:".$end.PHP_EOL;
echo "used:".($end - $start);

我们来看下start为1,end 分别为 10,100,1000,10000的情况下的内存消耗情况分别是怎么样的:

xrange(1,10); // used = 3480 0.0033187866210938MB
xrange(1,100); // used = 30168 0.028770446777344MB
xrange(1,1000); // used = 285144 0.27193450927734MB
xrange(1,10000); // used = 2957784 2.8207626342773MB

不难看出,随着$end的增大,所占用的内存也越来越大。

接下来我们来改造下xrange函数:

function xrange($start,$end,$step=1){  
  for($i=$start;$i<$end,$i += $step) {    
    yield $i;  
  }
}

我们删除了数组data,并且也删除了返回值,而在foreach的循环体里,在i前面添加关键字:yield。****

我们来看下改造之后的内存消耗:

xrange(1,10); // used = 256 0.000244140625MB
xrange(1,100); // used = 256 0.000244140625MB
xrange(1,1000); // used = 256 0.000244140625MB
xrange(1,10000); // used = 256 0.000244140625MB

Wow,这个结果令人惊讶。我们奇迹的发现了,内存消耗并没有随着$end的增大而增大,甚至是完全一样。

我们来还原一下代码的执行过程:

******首先调用xrange函数,传参$end=10,但是for循环了一次然后停止了,并且告诉foreach第一次循环可以用的值。


  1. ******foreach开始对$data循环,并使用for给的一个值执行输出。******
    
  2. ******foreach开始第二次循环,它向for循环又请求了一次******
    
  3. ******for循环又执行了一次,并将新的值告诉foreach
    

  4. ******foreach拿到第二个值,开始输出。
    

******所以,整个代码执行中,始终只有一个记录值参与循环,内存中也只有一条信息。


无论开始传入按的$end有多大,由于不会立即生成所有结果集,所以内存始终是一条循环的值,也就不会占用太大的内存了。

******看到这里,你是不是想说,“就这?”。生成器的用处当然不止这一些,还有其他的用武之地,比如协程。只不过因为本人才疏学浅,只能跟大家分享这么多了。大家感兴趣的话,可以看下鸟哥关于在php中使用协程实现多任务调度的文章。

好了,今天就到这里,如果大家觉得有用的话,不要忘记点赞收藏哦~

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分