1、概述
產生器 是PHP 5.5 引入的新特性,但是目測很少人用到它,其實這是個非常有用的功能。
產生器和迭代器有點類似,但是與標準的PHP迭代器不同,PHP產生器不要求類實現Iterator介面,從而減輕了類的開銷和負擔。產生器會根據需求每次計算併產出需要迭代的值,這對應用的效能有很大的影響:試想假如標準的PHP迭代器經常在記憶體中執行迭代操作,這要預先計算出資料集,效能低下;如果要使用特定方式計算大量資料,如操作Excel表資料,對效能影響更甚。此時我們可以使用產生器,即時計算併產出後續值,不佔用寶貴的記憶體空間。
2、建立產生器
產生器的建立方式很簡單,因為產生器就是PHP函數,只不過要在函數中一次或多次使用 yield 關鍵字。與普通的PHP函數不同的是,產生器從不傳回值,只產出值。下面是一個簡單的產生器實現:
function getLaravelAcademy() { yield 'http://LaravelAcademy.org'; yield 'Laravel學院'; yield 'Laravel Academy';}
很簡單吧!調用此產生器函數時,PHP會返回一個屬於Generator類的對象,這個對象可以使用 foreach 函數迭代,每次迭代,PHP會要求Generator執行個體計算並提供下一個要迭代的值。產生器的優雅體現在每次產出一個值之後,產生器的內部狀態都會停頓;向產生器請求下一個值時,內部狀態又會恢複。產生器內部的狀態會一直在停頓和恢複之間切換,直到抵達函數定義體的末尾或遇到空的 return 語句為止。我們可以使用下面的代碼調用并迭代上面定義的產生器:
foreach(getLaravelAcademy() as $yieldedValue) { echo $yieldedValue, PHP_EOL;}
上面代碼輸出如下:
http://LaravelAcademy.orgLaravel學院Laravel Academy
3、使用產生器
下面我們實現一個簡單的函數用於產生一個範圍內的數值,以此說明產生器是如何節省記憶體的。首先我們通過迭代器來實現:
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;}
此時執行會報錯,提示超出單個PHP進程記憶體限制(要為100萬個數字提供記憶體空間):
下面我們來改進實現方案,使用產生器實現如下:
function makeRange($length) { for ($i=0; $i<$length; $i++) { yield $i; }}foreach (makeRange(1000000) as $i) { echo $i . PHP_EOL;}
再次執行就可以毫無壓力的列印出結果,因為產生器每次只需要為一個整數分配記憶體。
此外,一個常用的使用案例就是使用產生器迭代流資源(檔案、音頻等)。假設我們想要迭代一個大小為4GB的CSV檔案,而虛擬私人伺服器(VPS)只允許PHP使用1GB記憶體,因此不能把整個檔案都載入到記憶體中,下面的代碼展示了如何使用產生器完成這種操作:
function getRows($file) { $handle = fopen($file, 'rb'); if ($handle == FALSE) { throw new Exception(); } while (feof($handle) === FALSE) { yield fgetcsv($handle); } fclose($handle);}foreach ($getRows($file) as $row) { print_r($row);}
上述樣本一次只會為CSV檔案中的一行分配記憶體,而不會把整個4GB的CSV檔案都讀取到記憶體中。
4、總結
產生器是功能多樣性和簡潔性之間的折中方案,產生器只是向前進的迭代器,這意味著不能使用產生器在資料集中執行後退、快進或尋找操作,只能讓產生器計算併產出下一個值。迭代大型資料集或數列時最適合使用產生器,因為這樣佔用的系統記憶體最少。產生器也能完成迭代器能完成的簡單任務,而且使用的代碼更少。
總而言之,產生器並沒有為PHP添加新功能,不過使用產生器大大簡化了某些任務,而且使用的記憶體更少,如果需要更多功能,例如在資料集中執行後退、快進以及尋找功能,最好自己編寫實現Iterator介面的類,或者使用PHP標準庫(SPL)中某個原生的迭代器( http://php.net/manual/spl.iterators.php )。