生成器的核心是一個yield關鍵字,一個生成器函數看起來像一個普通的函數,不同的是。普通函數返回一個值,而一個生成器可以yield生成許多它所需要的值。生成器函數被調用時,返回的是一個可以被遍歷的對象。yield和return有點類似,不過不同的是,return會返回值並且終止代碼的執行,而yield會返回一個值給循環調用此生成器的代碼並且只是暫停執行生成器函數。
<?php function get_one_to_three(){ for($i=1;$i<=3;$i++){ yield $i; } } $generator=get_one_to_three(); var_dump($generator); echo '<br/>'; var_dump($generator instanceof Iterator); echo '<br/>'; foreach ($generator as $value){ echo $value,'<br/>'; }
輸出結果:
調用get_one_to_three()的時候,里面的代碼並沒有真正的執行,而是返回了一個生產期對象$generator=Generator Object(),$genetator instanceof Iterator說明Generator實現了Iterator接口,可以用foreach進行遍歷,每次遍歷都會隱式調用current()、next()、key()、valid()等方法。(Generator類中的方法)
<?php 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接收值,然后繼續執行下一個yield public void throw(Exception $exception)//向生成器中拋入一個異常 public bool valid(void)//檢查迭代器是否被關閉,已被關閉返回FALSE,否則返回TRUE public void __wakeup(void)//序列化回調 public mixed getReturn(void)//返回generator函數的返回值,PHP version 7+ }
處理大數據
下面通過實現一個xrange函數來簡單說明:
<?php function xrange($start,$end,$step=1){ for($i=$start;$i<=$end;$i+=$step){ yield $i; } } foreach(xrange(1,10) as $num){ echo $num,'<br/>'; }
輸出結果:

上面這個xrange()函數提供了和PHP的內建函數range()一樣的功能,但是不同的是range()函數返回的是一個包含值從1到10的數組,而xrange()函數返回的是依次輸出的這些值得一個迭代器,而不會真正以數組形式返回。這種方法的優點是顯而易見的,它可以讓你在處理大數據集合的時候不用一次性的加載到內存中,甚至你可以處理無限大的數據流。
處理大文件
來優化下讀取大文件,在PHP讀取大文件的時候,經常會出現內存不足的情況,如果文件過大的話,沒法一次讀取完,采用yield來實現大文件的讀取。
老式讀取
function readLocalFile($fileName){ $handle=fopen($fileName,'r'); $lines=[]; 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'; } elseif ($bytes) { return round($bytes / 1024, 2) . 'kb'; } return round($bytes / 1048576, 2) . 'mb'; }
測試
//第一種 var_dump(readLocalFile('./test.txt')); echo '<br/>',formatBytes(memory_get_peak_usage()),'<br/>'; //第二種 $lines = readYieldFile('./test.txt'); foreach ($lines as $row) { echo $row,'<br/>'; } echo formatBytes(memory_get_peak_usage());
輸出結果:

總結
使用老式讀取,返回的是一個包含每行數據的數組,而yield方式則返回的是一個迭代器,而不會以真正的數組返回。
這種方法的優點是顯而易見的,它可以讓你在處理大數據集合的時候不用一次性的加載到內存中,甚至你可以處理無限大的數據流。
