愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

PHP生成器使用总结

生成器是自PHP5.5.0起引入的新特性,允许以更容易的方法来实现对象迭代。生成器允许在foreach代码块中写代码来迭代一组数据,而无需在内存中创建一个数组,减少内存开销

简单的示例

使用传统的range($start, $end [, $step = 1]),可能会造成较大的内存消耗。如range(0, 1000000),消耗的内存会超过100MB。而使用生成器,则能够解决这个问题:

function xrange($start, $limit, $step = 1) {
    if($start < $limit) {
        if($step <= 0) {
            throw new LogicException("Step must be +ve");
        }

        for($i=$start; $i<=$limit; $i+=$step) {
            yield $i;
        }
    } else {
        if($step >= 0) {
            throw new LogicException("Step must be -ve");
        }

        for($i=$start; $i>=$limit; $i+=$step) {
            yield $i;
        }
    }
}

此种情况下,运行以下代码效果一致:


foreach(range(1, 5, 2) as $val) {
    echo $val." ";
}
// 输出:1 3 5

foreach(xrange(1, 5, 2) as $val) {
    echo $val." ";
}
// 输出:1 3 5

生成器语法

1、生成器是通过yield关键字来返回值的,普通函数只能返回一个值,而使用yield则可以返回多个值。yield会返回一个可被遍历的对象,在每次需要的时候可以调用生成器函数,然后在产生一个值之后,状态将被保存下来,如此一来可以实现 调用一次就迭代一次的效果
2、如果要在一个表达式上下文中使用yield,则需要用圆括号包围起来,如$data = (yield $value),而不能使用$data = yield $value的形式
3、生成器可以同时生成键和值,使用yield $key => $value的形式,如:

$stuList = <<<'EOF'
14001 Tom
14002 Jack
14003 Michal
EOF;

function parseList($list) {
    $list = explode("\n", $list);
    foreach($list as $each) {
        $fields = explode(" ", $each);
        yield $fields[0] => $fields[1];
    }
}

echo "Students are:\n";
foreach(parseList($stuList) as $key => $val) {
    echo '['.$key.'] '.$val.PHP_EOL;
}
/*
输出:
[14001] Tom
[14002] Jack
[14003] Michal
*/

4、如果没有yield值的话,会生成null值,即yield;的情况下,迭代返回的是null
5、可以使用引用来生成值,会导致yield的值也随之变化,如:

function &gen() {
    $value = 3;
    while($value > 0) yield $value;
}

foreach(gen() as &$num) {
    echo ($num--)."\n";
}

6、在PHP中,可以使用yield from来从另一个生成器、Traversable对象、数组导入,来生成值,如:

function gen() {
    yield 1;
    yield from twoToThree();
    yield from [4, 5];
}

function twoToThree() {
    yield 2;
    yield 3;
}

foreach(gen() as $val) {
    echo $val." ";
}
// 将输出:1 2 3 4 5

生成器和Iterator接口对象对比

  • 使用生成器和遵循Iterator接口的对象,都可以实现迭代的效果。但是,相比Iterator对象,使用生成器最主要的优点就是简单、更少的代码就能实现,而且宽可读性更强。
  • 不过,生成器是只能前进的迭代器,一旦开始迭代后,就不能从头再来。而Iterator对象则能够使用rewind()方法,将迭代指针指向头部从而可以从头再开始迭代