学习PHP生成器yield及生成器类Generator功能

作者: 乘风御上者 分类: PHP 发布时间: 2020-07-23 14:15

生成器大部分功能实现自迭代器,所以理解生成器之前必须先学会迭代器的相关概念和功能。

// 很明显生成器类实现了迭代器接口的方法
Generator implements Iterator {
	
	// 实现迭代器的五个方法
	public current ( void ) : mixed
	public key ( void ) : mixed
	public next ( void ) : void
	public rewind ( void ) : void
	public valid ( void ) : bool

	// 向生成器中传入一个值,并且当做 yield 表达式的结果,然后继续执行生成器。
	public send ( mixed $value ) : mixed
	// 向生成器中抛入一个异常
	public throw ( Exception $exception ) : void
	// 阻止被序列化
	public __wakeup ( void ) : void
}

生成器类比较特殊,它不能被直接实例化,只能通过yield生成器生成Generator对象。

Generator类实现了迭代器的五个方法,使用方式无需多言,抛出异常的throw方法与阻止该对象被序列化的wakeup魔术方法也没什么好说的。
就剩一个send方法,它的作用是修改当前yield表达式的值,并返回下一个yield表达式的值。 相当于修改当前yield表达式的值后,执行next方法,然后执行current方法。

若还是不明白也不要紧,下面详细解读yield关键字,看完之后在回过头来理解就简单多了。

先说几点需要注意的地方:

  1. yield是生成器,Generator是生成器生成的对象。
  2. yield只能在函数中使用。
  3. 只要是使用yield的函数都将返回Generator对象。
  4. yield表达式可以赋值给变量,但需要使用括号包裹。
  5. yield功能类似return,但不会结束代码执行,只是作为一个记忆锚点,下次被触发(foreach)从此处继续执行。
// 使用yield生成器
function getGen(){
	yield '<br>打印1时间:'.time();
	yield '<br>打印2时间:'.time();
	yield '<br>打印3时间:'.time();
	yield '<br>打印4时间:'.time();
	yield '<br>打印5时间:'.time();
}

$arr = getGen();

// 返回一个生成器对象
var_dump($arr);

echo '<br>当前时间:'.time();
foreach ($arr as $v) {
	sleep(1);
	echo $v;
}

由此可以看出每个yield表达式的值,只有在foreach遍历时才会执行并返回一次结果。这样有什么好处呢?接着一个示例说明:

// 生成10W元素的数组
function getArr(){
	$arr = [];
	for ($i=0; $i < 100000; $i++) { 
		$arr[] = time();
	}
	return $arr;
}
$arr1 = getArr();
// 到此执行需要很长一段时间
// 而且$arr1中保存的大量数据在内存中
var_dump($arr1);


// 同样生成10W个元素
function getGenArr(){
	for ($i=0; $i < 100000; $i++) { 
		yield time();
	}
}
$arr2 = getGenArr();
// 速度非常快,没有多少内存开销
var_dump($arr2);

从此示例中可以看出,普通数组在元素大于一定量后效率变得极低。而生成器实例则不同,基本上没什么消耗。当遍历$arr2时,每一次遍历触发一次yield表达式,进而计算出结果。所以它的消耗内存始终只相当于一个元素的量。

什么场景下更实用呢? 比如读取大文件内容:

// 不会因为文件数据量大而报错
function readTxt(){

    $handle = fopen("测试文件路径", 'rb');
    while (feof($handle) === false) {
        yield fgets($handle);
    }
    fclose($handle);
}

$txtGen = readTxt();
foreach ($txtGen as $line => $txt) {
    echo .'<br />第'.$line.'行数据:'.$txt;
}

在框架中也用到了该功能,可以自行阅读相关功能。

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!

发表回复