PHP yield 生成器
yield 调用
PHP中的range() 函数在使用的时候会在内存中创建一个包含指定范围单元的数组并返回,一般来说,这个并没什么不妥,但是当所传的limit入参值很大的时候,那么也就意味着将会在内存中创建的数组也会很大,内存无法承受。此时我们可以通过生成器来实现一个更高效的range函数
function xrange($start, $limit, $step = 1) { //校验参数,此处省略 for ($i = $start; $i <= $limit; $i += $step) { //向外产出值 yield $i; } } //xrange此时返回的是一个生成器对象 $gen = xrange(1, 9); //对生成器进行迭代 foreach ($gen as $number) { echo "$number "; }
这里在xrange和range函数的效果相同,均是产生了一个可迭代的变量,但是不同的是,range函数有点像ORM里面常说的 预加载 ,而xrange则是懒加载只是等到 迭代到那个点(循环到当前的值再运行一次xrange 这个函数)才会产生对应的值,因此xrange并不需要分配大块内存来存放变量,大大节约了内存,提升效率。
yield 的特性
-
yield只能用于函数内部,在非函数内部运用会抛出错误。
-
如果函数包含了yield关键字称为"生成器函数",那么函数执行后的返回值(一个可遍历的对象)是一个Generator对象。
-
如果函数内部同事包含yield和return 该函数的返回值依然是Generator对象,但是在生成Generator对象时,return语句后的代码被忽略。
-
Generator类实现了Iterator接口。
-
可以通过返回的Generator对象内部的方法,获取到函数内部yield后面表达式的值。
-
可以通过Generator的send方法给yield 关键字赋一个值。
-
一旦返回的Generator对象被遍历完成,便不能调用他的rewind方法来重置
-
Generator对象不能被clone关键字克隆
-
yield表达式:$data =( yield) , 赋值需要使用括号包裹 yield {$value}
-
调用生成器函数的 send() 方法,通过 send 方法可以传递参数给 yield 发送一个值作为 yield 语句的值
send 方法使用示例:
function printer () { $a = (yield 1); yield $a; yield 2; yield 3; } $printer = printer (); echo $printer->current(); echo $printer->send("b"); $printer->next(); echo $printer->current();
输出结果:1 b 2;
send()方法会把值传递给上一个yield表达式的结果,然后再执行下一个yield并接受返回值。
首先执行到 $printer->current(); 此时返回的是第一个yield后面的1,所以输出1,然后停止。然后走到send("b"),这一步会把b赋值给(上一个yield表达式的结果)$a,然后继续执行一个yield表达式(yield $a;) 并接受返回值,所以再输出b,然后停止。紧接着执行next();生成器定位到yield 2; 然后执行 current()输出2。
生成器和普通函数有哪些异同
-
生成器中必须包含yield关键字(用来生成结果),而且可以是多次出现,普通函数中向外部返回结果只能使用return,且函数执行完毕;
-
一个生成器不可以通过return返回值,这样做会产生一个编译错误PHP Fatal error: Generators cannot return values using "return"(注意:这个在PHP7下面不会出错,但是会终止生成器继续执行,即调valid()方法会返回false,然而在PHP5中return空是一个有效的语法并且它将会终止生成器继续执行)
3. yield关键词,它最简单的调用形式看起来像普通函数的return,不同之处在于普通return会返回值并终止函数的执行,而yield会返回一个值给循环调用此生成器的代码,并且只是暂停执行生成器函数。
PHP yield 读取大文件
// 老式读取方式 function readLocalFile($fileName) { $handle = fopen($fileName, 'r'); $lins = []; while (!feof($handle)) { $lines[] = fgets($handle); } fclose($handle); return $lines; } // 使用 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'; }
测试,这里准备了一个 7M 大小的文本文件来做测试
# 第一种
readLocalFile('./all.txt');
echo formatBytes(memory_get_peak_usage()); // 结果为 7.59mb
# 第二种
$lines = readYieldFile('./all.txt');
foreach ($lines as $row) {}
echo formatBytes(memory_get_peak_usage()); // 结果为 137.79kb