迭代生成器

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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";
}

输出

1
2
3
object(Generator)#1 (0) { }
bool(true)
1 2 3

调用 gen_one_tow_three() 的时候,里面的代码并没有真正的执行,而是返回一个生成器对象 $generator = Generator Object()

$generator instanceof Iterator 说明 Generator 实现了 Iterator 接口,可以用 foreach 进行遍历,每次遍历都会隐式调用 current()next()key()valid() 等方法。(Generator 类中的方法)

处理大数据

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

1
2
3
4
5
6
7
8
9
10
11
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() 函数返回的是一个包含值从 1100w 的数组,而 xrange() 函数返回的是依次输出这些值得一个迭代器,而不会真正以数组形式返回。
这种方法的优点是显而易见的,它可以让你在处理大数据集合的时候不用一次性加载到内存中。甚至你可以处理无限大的数据流

处理大文件

老式读取

1
2
3
4
5
6
7
8
9
10
function readLocalFile($fileName) 
{
$handle = fopen($fileName, 'r');
$lines = [];
while (!feof($handle)) {
$lines[] = fgets($handle);
}
fclose($handle);
return $lines;
}

yield 读取方式

1
2
3
4
5
6
7
8
function readYieldFile($fileName)
{
$handle = fopen($fileName, 'r');
while (!feof($handle)) {
yield fgets($handle);
}
fclose($handle);
}

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

1
2
3
4
5
6
7
8
9
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";
}

老式读取方式测试

1
2
3
4
5
6
// 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

yield 读取方式测试

1
2
3
4
5
$lines = readYieldFile('./all.zip');
foreach($lines as $row){}
echo formatBytes(memory_get_peak_usage());

// 输出:141.3kb

总结

使用老式读取,返回的是一个包含每行数据的数组,而 yield 方式则返回的事宜个迭代器,而不会以真正的数组返回。
这种方法的有点可以让你在处理大数据集合的时候不用一次性加载到内存中,甚至可以处理无限大的数据流。