关于php:学习PHP生成器的使用

3次阅读

共计 4150 个字符,预计需要花费 11 分钟才能阅读完成。

什么是生成器?

听着高大上的名字,感觉像是发明什么货色的一个性能,实际上,生成器是一个用于迭代的迭代器。它提供了一种更容易的形式来实现简略的对象迭代,相比拟定义类实现 Iterator 接口的形式,性能开销和复杂性大大降低。

说了半天不如间接看看代码更直观。

function test1()
{for ($i = 0; $i < 3; $i++) {yield $i + 1;}
    yield 1000;
    yield 1001;
}

foreach (test1() as $t) {echo $t, PHP_EOL;}

// 1
// 2
// 3
// 1000
// 1001

就是这么简略的一段代码。首先,生成器必须在办法中并应用 yield 关键字;其次,每一个 yield 能够看作是一次 return;最初,内部循环时,一次循环取一个 yield 的返回值。在这个例子,循环三次返回了 1、2、3 这三个数字。而后在循环内部又写了两行 yield 别离输入了 1000 和 1001。因而,内部的 foreach 一共循环输入了五次。

很神奇吧,明明是一个办法,为什么可能循环它而且还是很奇怪的一种返回循环体的格局。咱们间接打印这个 test() 办法看看打印的是什么:

// 是一个生成器对象
var_dump(test1());

// Generator Object
// (//)

当应用了 yield 进行内容返回后,返回的是一个 Generator 对象。这个对象就叫作生成器对象,它不能间接被 new 实例化,只能通过生成器函数这种形式返回。这个类蕴含 current()、key() 等办法,而且最次要的这个类实现了 Iterator 接口,所以,它就是一个非凡的迭代器类。

Generator implements Iterator {
    /* 办法 */
    public current (void) : mixed
    public key (void) : mixed
    public next (void) : void
    public rewind (void) : void
    public send (mixed $value) : mixed
    public throw (Exception $exception) : void
    public valid (void) : bool
    public __wakeup (void) : void
}

生成器有什么用?

搞了半天不就是个迭代器嘛?搞这么麻烦干嘛,间接用迭代器或者在办法中间接返回一个数组不就好了吗?没错,失常状况下真的没有这么麻烦,然而如果是在数据量特地大的状况下,这个生成器就能施展它的弱小威力了。生成器最最弱小的局部就在于,它不须要一个数组或者任何的数据结构来保留这一系列数据。每次迭代都是代码执行到 yield 时动静返回的。因而,生成器可能极大的节约内存。

// 内存占用测试
$start_time = microtime(true);
function test2($clear = false)
{$arr = [];
    if($clear){
        $arr = null;
        return;
    }
    for ($i = 0; $i < 1000000; $i++) {$arr[] = $i + 1;
    }
    return $arr;
}
$array = test2();
foreach ($array as $val) {
}
$end_time = microtime(true);

echo "time:", bcsub($end_time, $start_time, 4), PHP_EOL;
echo "memory (byte):", memory_get_usage(true), PHP_EOL;

// time: 0.0513
// memory (byte): 35655680

$start_time = microtime(true);
function test3()
{for ($i = 0; $i < 1000000; $i++) {yield $i + 1;}
}
$array = test3();
foreach ($array as $val) {

}
$end_time = microtime(true);

echo "time:", bcsub($end_time, $start_time, 4), PHP_EOL;
echo "memory (byte):", memory_get_usage(true), PHP_EOL;

// time: 0.0517
// memory (byte): 2097152

上述代码只是简略的进行 1000000 个循环后获取后果,不过也能够直观地看出。应用生成器的版本仅仅耗费了 2M 的内存,而未应用生成器的版本则耗费了 35M 的内存,间接曾经 10 多倍的差距了,而且越大的量差距超显著。因而,有大神将生成器说成是 PHP 中最被低估了的一个个性。

生成器的利用

接下来咱们来看看生成器的一些根本的利用形式。

返回空值以及中断

生成器当然也能够返回空值,间接 yield; 不带任何值就能够返回一个空值了。而在办法中间接应用 return; 也能够用来中断生成器的继续执行。上面的代码咱们在 \$i = 4; 的时候返回的是个空值,也就是不会输入 5(因为咱们返回的是 $i + 1)。而后在 $i == 7 的时候应用 return; 中断生成器的继续执行,也就是循环最多只会输入到 7 就完结了。

// 返回空值以及中断
function test4()
{for ($i = 0; $i < 10; $i++) {if ($i == 4) {yield; // 返回 null 值}
        if ($i == 7) {return; // 中断生成器执行}
        yield $i + 1;
    }
}

foreach (test4() as $t) {echo $t, PHP_EOL;}


// 1
// 2
// 3
// 4

// 5
// 6
// 7

返回键值对模式

不要诧异,生成器真的是能够返回键值对模式的可遍历对象供 foreach 应用的,而且语法十分好记:yield key => value; 是不是和数组项的定义模式截然不同,十分直观好了解。

function test5()
{for ($i = 0; $i < 10; $i++) {yield 'key.' . $i => $i + 1;}
}

foreach (test5() as $k=>$t) {echo $k . ':' . $t, PHP_EOL;}

// key.0:1
// key.1:2
// key.2:3
// key.3:4
// key.4:5
// key.5:6
// key.6:7
// key.7:8
// key.8:9
// key.9:10

内部传递数据

咱们能够通过 Generator::send 办法来向生成器中传入一个值。传入的这个值将会被当做生成器以后 yield 的返回值。而后咱们依据这个值能够做一些判断,比方依据内部条件中断生成器的执行。

function test6()
{for ($i = 0; $i < 10; $i++) {
        // 失常获取循环值,当内部 send 过去值后,yield 获取到的就是内部传来的值了
        $data = (yield $i + 1);
        if($data == 'stop'){return;}
    }
}
$t6 = test6();
foreach($t6 as $t){if($t == 3){$t6->send('stop');
    }
    echo $t, PHP_EOL;
}

// 1
// 2
// 3

上述代码了解起来可能比拟绕,然而留神记住正文的那行话就行了(失常获取循环值,当内部 send 过去值后,yield 获取到的就是内部传来的值了)。另外,变量获取 yield 的值,必须要用括号括起来。

yield from 语法

yield from 语法其实就是指的从另一个可迭代对象中一个一个的获取数据并造成生成器返回。间接看代码。

function test7()
{yield from [1, 2, 3, 4];
    yield from new ArrayIterator([5, 6]);
    yield from test1();}
foreach (test7() as $t) {echo 'test7:', $t, PHP_EOL;}

// test7:1
// test7:2
// test7:3
// test7:4
// test7:5
// test7:6
// test7:1
// test7:2
// test7:3
// test7:1000

在 test7() 办法中,咱们应用 yield from 别离从一般数组、迭代器对象、另一个生成器中获取数据并做为以后生成器的内容进行返回。

小惊喜

生成器能够用 count 获取数量吗?

道歉,生成器是不能用 count 来获取它的数量的。

$c = count(test1()); // Warning: count(): Parameter must be an array or an object that implements Countable
// echo $c, PHP_EOL;

应用 count 来获取生成器的数量将间接报 Warning 正告。间接输入将会始终显示是 1,因为 count 的个性(强制转换成数组都会显示 1)。

应用生产器来获取斐波那契数列

// 利用生成器生成斐波那契数列
function fibonacci($item)
{
    $a = 0;
    $b = 1;
    for ($i = 0; $i < $item; $i++) {
        yield $a;
        $a = $b - $a;
        $b = $a + $b;
    }
}

$fibo = fibonacci(10);
foreach ($fibo as $value) {echo "$value\n";}

这段代码就不多解释了,十分直观的一段代码了。

总结

生成器相对是 PHP 中的一个暗藏的宝藏,不仅是对于内存节约来说,而且语法其实也十分的简洁明了。咱们不须要在办法外部再多定义一个数组去存储返回值,间接 yield 一项一项的返回就能够了。在理论的我的项目中齐全值得尝试一把,然而尝试完了别忘了和小伙伴们分享,大部分人可能真的没有接触过这个个性哦!!

测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/php/202002/source/%E5%AD%A6%E4%B9%A0PHP%E7%94%9F%E6%88%90%E5%99%A8%E7%9A%84%E4%BD%BF%E7%94%A8.php

参考文档:
https://www.php.net/manual/zh/language.generators.overview.php
https://www.php.net/manual/zh/class.generator.php

===========

各自媒体平台均可搜寻【硬核项目经理】

正文完
 0