深入了解PHP中生成器yield的使用

 更新时间:2022年07月19日 10:38:08   作者:PHP开源社区  
如果是做Python或者其他语言的小伙伴,对于生成器应该不陌生。但很多PHP开发者或许都不知道生成器这个功能,本文就来和大家详细讲讲

如果是做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()    继续往下执行生成器

到此这篇关于深入了解PHP中生成器yield的使用的文章就介绍到这了,更多相关PHP生成器yield内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • PHP错误Cannot use object of type stdClass as array in错误的解决办法

    PHP错误Cannot use object of type stdClass as array in错误的解决办法

    很多人在PHP输出一个二维数组的时候出现“Fatal error: Cannot use object of type stdClass as array in……”。这篇文章主要介绍了该错误的解决办法,需要的朋友可以参考下
    2014-06-06
  • PHP Document 代码注释规范

    PHP Document 代码注释规范

    PHPDocumentor是一个用PHP写的工具,对于有规范注释的php程序,它能够快速生成具有相互参照,索引等功能的API文档。老的版本是 phpdoc。
    2009-04-04
  • 解析PHP对现有搜索引擎的调用

    解析PHP对现有搜索引擎的调用

    本篇文章是关于PHP对现有搜索引擎的调用进行了详细的分析介绍,需要的朋友参考下
    2013-06-06
  • php商品对比功能代码分享

    php商品对比功能代码分享

    前段时间,朋友叫我帮他写个商品对比功能,当时正好在逛绿森数码商城,点开商品列表,看到有商品对比功能,然后直接下载了他的JS滚动文件和抽出了对比框的样式,然后自己完善了下。
    2015-09-09
  • 学习使用curl采集curl使用方法

    学习使用curl采集curl使用方法

    从朋友那拿到一份代码,用CURL模拟用户登录并采集需要用户登录的页面的
    2012-01-01
  • PHP折半(二分)查找算法实例分析

    PHP折半(二分)查找算法实例分析

    这篇文章主要介绍了PHP折半(二分)查找算法,结合实例形式较为详细的分析了php折半(二分)查找算法的概念、原理、实现与使用方法,并附带了一个php折半(二分)查找算法类供大家参考,需要的朋友可以参考下
    2018-05-05
  • linux下删除7天前日志的代码(php+shell)

    linux下删除7天前日志的代码(php+shell)

    shell 版本比较麻烦 关键我linux转换不熟悉
    2011-01-01
  • php中explode函数用法分析

    php中explode函数用法分析

    这篇文章主要介绍了php中explode函数用法,实例分析了explode函数分割字符串及获取文件后缀名等应用,具有一定的参考借鉴价值,需要的朋友可以参考下
    2014-11-11
  • php实现的简易扫雷游戏实例

    php实现的简易扫雷游戏实例

    这篇文章主要介绍了php实现的简易扫雷游戏,涉及php字符串及流程控制的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • 微信公众号开发之微信公共平台消息回复类实例

    微信公众号开发之微信公共平台消息回复类实例

    这篇文章主要介绍了微信公众号开发之微信公共平台消息回复类,给出了其完整实例,并附有注释说明便于理解与运用,对于微信公众号的开发来说非常具有参考借鉴价值,需要的朋友可以参考下
    2014-11-11

最新评论