一、生成器介绍

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

生成器函数被调用时,返回的是一个可以被遍历的对象。

例子:

function gen_one_to_three() {
    for ($i = 1; $i <= 3; $i++) {
        //注意变量$i的值在不同的yield之间是保持传递的。
        yield $i;
    }
}

运行输出

$generator = gen_one_to_three();

var_dump($generator); # object(Generator)#1 (0) {}

var_dump($generator instanceof Iterator); # bool(true)

foreach ($generator as $value) {
    echo "$value\n"; # 1 2 3
}

当调用gen_one_to_three()函数的时候,返回了一个生成器对象。$generator instanceof Iterator 说明Generator实现了Iterator接口,可以使用foreach进行遍历,每次遍历都会隐式调用current()、next()、key()、valid()等方法。

Generator 是实现Iterator

Generator implements Iterator {
    /* 方法 */
    public mixed current ( void )
    public mixed key ( void )
    public void next ( void )
    public void rewind ( void )
    public mixed send ( mixed $value ) # 向生成器中传入一个值,并且当做 yield 表达式的结果,然后继续执行生成器。
    public void throw ( Exception $exception )
    public bool valid ( void )
    public void __wakeup ( void )
}

二、处理大数据

先来看看xrange函数来简单说明下

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

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

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

使用生成器的有点就在于,它让你在处理大数据集合的时候不用一次性加载数据到内存中,甚至你可以处理无线大的数据流。

三、处理大文件

php读取大文件的时候,经常会出现内存不足的情况。这样文件太大,就没办法一次性读完一个文件,采用yield来实现大文件的读取

老式读取

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

YIELD读取方式

function readYieldFile($fileName){
    $handle = fopen($fileName, 'rb');
    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";
}

复制一个大文件来测试

dd if=/dev/zero of=./all.txt bs=1m count=1000 # 每次读取1m,读1000次

测试

# 第一种
readLocalFile("./all.txt");
echo formatBytes(memory_get_peak_usage());

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

总结
使用老式读取,返回的是一个包含每行数据的数组,而yield方式则返回的是一个迭代器,而不会以真正的数组返回。
使用yield处理大数据集合的时候,不用一次新的加载到内存,甚至可以处理无限大的数据流。