PHP生成器是5.5.0引入的功能,生成器實(shí)際上就是簡(jiǎn)單的迭代器。生成器會(huì)根據(jù)需求計(jì)算產(chǎn)出迭代的值,而標(biāo)準(zhǔn)的PHP迭代器經(jīng)常在內(nèi)存中執(zhí)行迭代操作,這要預(yù)先計(jì)算出數(shù)據(jù)集,性能較低。如果使用特定的防護(hù)計(jì)算大量數(shù)據(jù),可以使用生成器,即時(shí)計(jì)算并產(chǎn)出后續(xù)值,不占用內(nèi)存。
創(chuàng)建生成器
生成器從不返回值,只是產(chǎn)出值。
<?php function myGenerator() { yield 'v1'; yield 'v2'; yield 'v3'; }
調(diào)用生成器函數(shù)時(shí),PHP會(huì)反悔一個(gè)屬于Generator類的對(duì)象。這個(gè)對(duì)象是可以foreach迭代的。每次迭代,PHP要求這個(gè)實(shí)例計(jì)算并提供下一個(gè)要迭代的值。
每次產(chǎn)出一個(gè)值,生成器的內(nèi)部狀態(tài)都會(huì)停頓。向生成器請(qǐng)求下一個(gè)值時(shí),內(nèi)部狀態(tài)才會(huì)恢復(fù)。這種停頓-恢復(fù)的狀態(tài)會(huì)一直持續(xù)下去。
<?php foreach (myGenerator() as $yieldValue) { echo $yieldValue , PHP_EOL; }
使用生成器
<?php function makeRange($length) { $dataset = []; for ($i = 0; $i < $length; $i++) { $dataset[] = $i; } return $dataset; } $customRange = makeRange(1000000); foreach ($customRange as $i) { echo $i, PHP_EOL; }
上面的這個(gè)方法并沒有善用內(nèi)存,使用生成器只會(huì)為一個(gè)整數(shù)分配內(nèi)存。
<?php function makeRange($length) { for ($i = 0; $i < $length; $i++) { yield $i; } } foreach(makeRange(1000000) as $i) { echo $i, PHP_EOL; }
應(yīng)用場(chǎng)景
很多PHP開發(fā)者不了解生成器,其實(shí)主要是不了解應(yīng)用場(chǎng)景。那么,生成器在實(shí)際開發(fā)中有哪些應(yīng)用?
PHP開發(fā)很多時(shí)候都要讀取大文件,比如csv文件、txt文件,或者一些日志文件。這些文件如果很大,比如5個(gè)G。這時(shí),直接一次性把所有的內(nèi)容讀取到內(nèi)存中計(jì)算不太現(xiàn)實(shí)。
這里生成器就可以派上用場(chǎng)啦。簡(jiǎn)單看個(gè)例子:
<?php function getRows($file) { $handle = fopen($file, 'rb'); if ($handle === false) { throw new Exception(); } while (feof($handle) === false) { yield fgetcsv($handle); } fclose($handle); } foreach (getRows('data.csv') as $row) { print_r($row); }
這個(gè)例子中,生成器只會(huì)為CSV文件分配一行內(nèi)存,而不是讀入整個(gè)文件到內(nèi)存。使用生成器讀取文件,第一次讀取了第一行,第二次讀取了第二行,以此類推,每次被加載到內(nèi)存中的文字只有一行,大大的減小了內(nèi)存的使用。這樣,即使讀取上G的文本也不用擔(dān)心,完全可以像讀取很小文件一樣編寫代碼。