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、使用引用来生成值
在生成器函数前面加&符号
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结果集
…