学习PHP生成器yield及生成器类Generator功能
生成器大部分功能实现自迭代器,所以理解生成器之前必须先学会迭代器的相关概念和功能。
// 很明显生成器类实现了迭代器接口的方法
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关键字,看完之后在回过头来理解就简单多了。
先说几点需要注意的地方:
- yield是生成器,Generator是生成器生成的对象。
- yield只能在函数中使用。
- 只要是使用yield的函数都将返回Generator对象。
- yield表达式可以赋值给变量,但需要使用括号包裹。
- 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;
}
在框架中也用到了该功能,可以自行阅读相关功能。