PHP生成器与yield

https://www.php.net/manual/zh/class.generator.php
https://www.php.net/manual/zh/language.generators.overview.php

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
}

Generator::current — 返回当前产生的值
Generator::key — 返回当前产生的键
Generator::next — 生成器继续执行
Generator::rewind — 重置迭代器
Generator::send — 向生成器中传入一个值
Generator::throw — 向生成器中抛入一个异常
Generator::valid — 检查迭代器是否被关闭
Generator::__wakeup — 序列化回调

生成器的核心思想是:需要用到的时候才产生数据,这使得它不需要将大量数据加载进内存,内存开销很小。

yield 产生一个值
return 终止生成器。

一个生成器不可以返回值: 这样做会产生一个编译错误。然而return空是一个有效的语法并且它将会终止生成器继续执行,实际上只要有return操作,就变成了普通的函数了。

在内部会为生成的值配对连续的整型索引,就像一个非关联的数组。

PHP生成器不能满足所有迭代器的需求,因为如果不查询,生成器永远不知道下一个要迭代的值是什么,在生成器中无法后退或前进。

生成器还是一次性的,无法多次迭代同一个生成器,不过,如果需要,可以重建或克隆生成器。

1、使用yield赋值,小括号是必须的。

$data = (yield $value);

传递null值

$string = yield;

2、生成键值对
生成器默认会有从0开始的整型键值,你也可以自定义键值

yield $key => $val;

$data = (yield $key => $value);

3、生成null值

yield;

4、使用引用来生成值
在生成器函数前面加&符号

扫描二维码关注公众号,回复: 8664802 查看本文章
function &gen_reference() {
    $value = 3;

    while ($value > 0) {
        yield $value;
    }
}

/*
 * 我们可以在循环中修改$number的值,而生成器是使用的引用值来生成,所以gen_reference()内部的$value值也会跟着变化。
 */
foreach (gen_reference() as &$number) {
    echo (--$number).'... ';
}

以上例程会输出:

2… 1… 0…

5、使用yield from获取另外一个生成器,可迭代对象,数组的值

function count_to_ten() {
    yield 1;
    yield 2;
    yield from [3, 4];
    yield from new ArrayIterator([5, 6]);
    yield from seven_eight();
    yield 9;
    yield 10;
}

function seven_eight() {
    yield 7;
    yield from eight();
}

function eight() {
    yield 8;
}

foreach (count_to_ten() as $num) {
    echo "$num ";
}

打印:

1 2 3 4 5 6 7 8 9 10

6、向生成器传递值
可以通过生成器对象的send方法向生成器里面传递值当做 yield 表达式的结果,然后继续执行生成器。

function printer() {
    while (true) {
        $string = yield;
        echo $string;
    }
}

$printer = printer();
$printer->send('Hello world!');

以上例程会输出:

Hello world!

再例如:

function printer() {
    for($i=0;$i<10;$i++){
    	$temp = (yield $i);
    	if($temp == 'stop'){
    		return;
    	}
    }
}

$gen = printer();

foreach($gen as $v){
	echo $v.PHP_EOL;

	if($v == 5){
		$gen->send('stop');
	}
}

以上例程会输出:

0
1
2
3
4
5

示例1:

function test1(){
	$data = [];
	for($i=0; $i<10; $i++){
		$data[$i] = time();
	}
	return $data;
}

function test2(){
	for($i=0; $i<10; $i++){
		yield time();
	}
}

function get1(){
	$data = test1();

	foreach ($data as $key => $value) {
		sleep(1);
		echo $value . PHP_EOL;
	}
}

function get2(){
	$generator = test2();
	foreach($generator as $v){
		sleep(1);
		echo $v . PHP_EOL;
	}
}

get1();

echo '----------------------'.PHP_EOL;

get2();

打印信息:

1579230134
1579230134
1579230134
1579230134
1579230134
1579230134
1579230134
1579230134
1579230134
1579230134
-----------------------------------------
1579230144
1579230145
1579230146
1579230147
1579230148
1579230149
1579230150
1579230151
1579230152
1579230153

这个例子很好的说明了“用到的时候才产生数据”的含义。

示例2:读取文件

/**
 * 查看文件大小:ll -lh apache-jmeter-4.0.zip
 * 50M
 */

$file = 'apache-jmeter-4.0.zip';

read1($file);
echo "------------------------------------".PHP_EOL;
read2($file);
echo "------------------------------------".PHP_EOL;
read3($file);

function read1($file){
    $mem_start = memory_get_usage();
    $time_start = microtime(true);

    $cont = file_get_contents($file);
    $arr = explode(PHP_EOL, $cont);
    $n = 0;
    foreach($arr as $k => $v){
        $n++;
    }
    var_dump($n);

    $time_end = microtime(true);
    $mem_end = memory_get_usage();
    echo '1-使用内存:'.round(($mem_end - $mem_start)/1024/1024, 2).'MB'.PHP_EOL;
    echo '1-使用时间:'.round($time_end - $time_start, 2).PHP_EOL;
}

function read2($file){
    $mem_start = memory_get_usage();
    $time_start = microtime(true);

    $n = 0;
    $generator = readTxt($file);
    foreach($generator as $k => $v){
        $n++;
    }
    var_dump($n);

    $time_end = microtime(true);
    $mem_end = memory_get_usage();
    echo '2-使用内存:'.round(($mem_end - $mem_start)/1024/1024, 2).'MB'.PHP_EOL;
    echo '2-使用时间:'.round($time_end - $time_start, 2).PHP_EOL;
}

function readTxt($file){
    $hand = fopen($file, 'r');
    var_dump($hand); // 只会打印一次
    while(!feof($hand)){
        yield fgets($hand);
    }
    fclose($hand);
}

function read3($file){
    $mem_start = memory_get_usage();
    $time_start = microtime(true);

    $hand = fopen($file, 'r');
    $n = 0;
    while(!feof($hand)){
        fgets($hand);
        $n++;
    }
    var_dump($n);

    $time_end = microtime(true);
    $mem_end = memory_get_usage();
    echo '3-使用内存:'.round(($mem_end - $mem_start)/1024/1024, 2).'MB'.PHP_EOL;
    echo '3-使用时间:'.round($time_end - $time_start, 2).PHP_EOL;
}

打印:

int(792)
1-使用内存:100.71MB
1-使用时间:0.49
------------------------------------
resource(6) of type (stream)
int(199157)
2-使用内存:0MB
2-使用时间:0.11
------------------------------------
int(199157)
3-使用内存:0.01MB
3-使用时间:0.1

可以看出,在读物文件的时候,生成器和while都可以达到优化内存使用和执行性能的效果,但是迭代器提供了一种更友好的,易于扩展的操作的迭代器。

示例3:替代range函数

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

foreach(xrange(1, 10, 1) as $v){
	echo $v.PHP_EOL;
}

调用 range(0, 1000000) 将导致内存占用超过 100 MB,而 xrange(0, 1000000) 只需要不到1K字节的内存。

示例4:使用生成器遍历Mysqli和PDO结果集

发布了416 篇原创文章 · 获赞 25 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/raoxiaoya/article/details/104020231