PHP迭代生成器---yield

1、迭代生成器

生成器的核心是一个yield关键字,一个生成器函数看起来像一个普通的函数,不同的是:普通函数返回一个值,而一个生成器可以yield生成许多它所需要的值。生成器函数被调用时,返回的是一个可以被遍历的对象。

yield和return有点类似,不过不同的是,return会返回值并且终止代码的执行,而yield会返回一个值给循环调用此生成器的代码并且只是暂停执行生成器函数。

function gen_one_tow_three(){
    for($i = 1; $i <=3; $i++){
        //注意:变量$i的值在不同的yield之间是保持传递的。
        yield $i;
    }
}
$generator = gen_one_tow_three();
var_dump($generator);
echo "<br>";
var_dump($generator instanceof Iterator);
echo "<br>";
foreach($generator as $value){
    echo $value."\n";
}        

输出:

object(Generator)#1 (0) { }

bool(true)

1 2 3

调用gen_one_tow_three()的时候,里面的代码并没有真正的执行,而是返回一个生成器对象$generator = Generator Object(),$generator instanceof Interator说明Generator事项了Iterator接口,可以用foreach进行遍历,每次遍历都会隐式调用current()、next()、key()、valid()等方法。(Generator类中的方法)

2、处理大数据

下面通过实现一个xrange函数来简单说明:

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

foreach(xrange(1,1000000) as $num){
    echo $num,"\n";
}

上面这个xrange()函数提供了和php的内建函数range()一样的功能,但不同的是range()函数返回的是一个包含值从1到100w的数组,而xrange()函数返回的是依次输出这些值得一个迭代器,而不会真正以数组形式返回。

这种方法的优点是显而易见的,它可以让你在处理大数据集合的时候不用一次性的加载到内存中。甚至你可以处理无限大的数据流

3、处理大文件

在PHP读取大文件的时候,经常会出现内存不足的情况,如果文件过大的话,没法一次读取玩,采用yield来实现大文件的读取。

3.1)老式读取

function readLocalFile($fileName){
    $handle = fopen($fileName,'r');
    $lines = [];
    while(!feof($handle)){
        $lines[] = fgets($handle);
    }
    fclose($handle);
    return $lines;
}

3.2)yield读取方式

function readYieldFile($fileName){
    $handle = fopen($fileName,'r');
    while(!feof($handle)){
        yield fgets($handle);
    }
    fclose($handle);
}

//为了便于测试,写一个读取内存的辅助函数

function formatBytes($bytes){
    if($bytes<1024){
        return $bytes . "b";
    }else if($bytes <1048576){
        return round($bytes / 1024,2) ."kb";
    }
    return round($bytes / 1048576,2) . "mb";
}
//第一种,all.zip是一个158M的压缩文件
readLocalFile('./all.zip');
echo formatBytes(memory_get_peak_usage());

报错:Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 123 bytes) in

//第二种
$lines = readYieldFile('./all.zip');
foreach($lines as $row){}
echo formatBytes(memory_get_peak_usage()); 

输出:141.3kb

总结:

使用老式读取,返回的是一个包含每行数据的数组,而yield方式则返回的事宜个迭代器,而不会以真正的数组返回。

这种方法的有点可以让你在处理大数据集合的时候不用一次性的加载到内存中,甚至可以处理无限大的数据流。