php生成器函式與yield關鍵字

wwwxgb66com17176934555發表於2021-07-19

初次接觸迭代器與生成器是在Python中,之後瞭解到在 php5.5 中也引入了生成器的特性,
但很多PHP開發者或許都不知道生成器這個功能,可能是因為平時使用場景較少吧。
但是,生成器功能的確非常有用。

優點:

  • 生成器會對PHP應用的效能有非常大的影響

  • PHP程式碼執行時節省大量的記憶體

  • 比較適合計算大量的資料

使用一個簡單的例子說明(迭代輸出從1開始到10000的陣列,步進為1):

<?php$start_mem = memory_get_usage();$arr = range( 1, 10000 );foreach( $arr as $value ){    //echo $value.',';}$end_mem = memory_get_usage();echo " use mem : ". ( $end_mem - $start_mem ) .'bytes'.PHP_EOL;

輸入結果:

圖片

<?php$start_mem = memory_get_usage();function xrange($start, $limit, $step = 1) {    for ($i = $start; $i <= $limit; $i += $step) {        // 注意變數$i的值在不同的yield之間是保持傳遞的。        yield $i;    }}foreach( xrange( 0, 10000 ) as $value ){    echo $value.PHP_EOL;}$end_mem = memory_get_usage();echo " use mem : ". ( $end_mem - $start_mem ) .'bytes'.PHP_EOL;

輸入結果:

首先從執行結果上來看,528440bytes32bytes這兩個記憶體消耗就一目瞭然了,
在生成器中提供了一種更容易的方法來實現簡單的物件迭代(迴圈),
相比較定義類實現 Iterator 介面的方式,效能開銷和複雜性大大降低。


相關程式碼剖析

這裡使用示例程式碼,在命令列下執行,加上 sleep(1) 可使執行結果更加明顯

<?phpfunction xrange($start, $limit, $step = 1) {    // echo '生成器開始執行' . PHP_EOL;    for ($i = $start; $i <= $limit; $i += $step) {        // echo '產生資料之前:' . $i  . PHP_EOL;        yield $i;        // echo '產生資料之後:' . $i  . PHP_EOL;    }    // echo '再來一個資料' . PHP_EOL;    yield 100;    // echo '生成器執行結束' . PHP_EOL;}$arr = xrange( 0, 5 );// echo '生成器開始執行了嗎?' . PHP_EOL;sleep(1);foreach( $arr as $value ){    sleep(1);    // echo '使用資料前' . PHP_EOL;    echo '使用資料:' . $value . PHP_EOL;    // echo '使用資料後' . PHP_EOL;}

輸出結果:

使用資料:0使用資料:1使用資料:2使用資料:3使用資料:4使用資料:5使用資料:100

我們可以看到資料在一行一行的輸出,接著我們去掉程式碼中的註釋,再次執行一遍

輸出結果:

生成器開始執行了嗎?生成器開始執行產生資料之前:0使用資料前使用資料:0使用資料後產生資料之後:0產生資料之前:1使用資料前使用資料:1使用資料後產生資料之後:1...產生資料之前:5使用資料前使用資料:5使用資料後產生資料之後:5再來一個資料使用資料前使用資料:100使用資料後生成器執行結束

還原一下程式碼執行過程:

  1. 首先呼叫 xrange 函式(生成器),傳入( 0, 5 ),這裡我們看到生成器並沒有開始執行

  2. foreach 開始對 $arr 迴圈,執行生成器,接著 for 產生第一個資料,將資料 0 返回到 foreach中,第一次 for 迴圈結束

  3. foreach 準備第二次迴圈,接著 for 產生第二個資料,將資料 1 返回到 foreach 中,第二次 foreach 迴圈結束,第二次 for 迴圈結束

  4. 這裡 foreach 迴圈迴圈 6 次,for 迴圈六次,至此 for 迴圈結束

  5. foreach 迴圈第七次,輸出生成器中最後一個數 100,到此 foreach 迴圈結束

從程式碼中我們看到,始終只有一個記錄值參與迴圈,記憶體中也只有一條資訊。
無論開始傳入的 $arr 有多大,由於並不會立即生成所有結果集,所以記憶體始終是一條迴圈的值

生成器函式的核心 – yield關鍵字

yield 最簡單的呼叫形式看起來像一個 return 申明,不同之處在於普通 return 會返回值並終止函式的執行,而 yield 會返回一個值給迴圈呼叫此生成器的程式碼並且只是暫停執行生成器函式。。


概念理解

到這裡,你應該已經大概理解什麼是生成器了。下面我們來說下生成器原理。

首先明確一個概念:生成器 yield 關鍵字不是返回值,他的專業術語叫產出值,只是生成一個值

那麼程式碼中 foreach 迴圈的是什麼?其實是PHP在使用生成器的時候,會返回一個 Generator 類的物件。foreach 可以對該物件進行迭代,每一次迭代,PHP會通過 Generator 例項計算出下一次需要迭代的值。這樣 foreach 就知道下一次需要迭代的值了。

而且,在執行中 for 迴圈執行後,會立即停止。等待 foreach 下次迴圈時候再次和 for 索要下次的值的時候,for 迴圈才會再執行一次,然後立即再次停止。直到不滿足條件不執行結束。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章