PHP 生成器的使用详解

什么是生成器?

听着高大上的名字,感觉像是创造什么东西的一个功能,实际上,生成器是一个用于迭代的迭代器。它提供了一种更容易的方式来实现简单的对象迭代,相比较定义类实现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=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

外部传递数据

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygdsy.html