淺入理解 PHP 中的 Generator

何爲 Generator

從 PHP 5.5 開始,PHP 加入了一個新的特性,那就是 Generator,中文譯爲生成器。生成器能夠簡單地用來實現對象的迭代,讓咱們先從官方的一個小例子提及。php

xrange

在 PHP 中,咱們都知道,有一個函數叫作 range,用來生成一個等差數列的數組,而後咱們能夠用這個數組進行 foreach 的迭代。具體就想這樣。數組

foreach (range(1, 100, 2) as $num) {
    echo $num . PHP_EOL;
}

這一段代碼就會輸出首項爲 1,末項爲 100,公差爲 2 的等差數列。它的執行順序是這樣的。首先,range(1, 100, 2) 會生成一個數組,裏面存了上面那樣的一個等差數列,以後在 foreach 中對這個數組進行迭代。ide

那麼,這樣就會出現一個問題,若是我要生成 100 萬個數字呢?那咱們就要佔用上百兆內存。雖然如今內存很便宜,可是咱們也不能這麼浪費內存嘛。那麼這時,咱們的生成器就能夠排上用場了。考慮下面的代碼。函數

function xrange($start, $limit, $step = 1) {
    while ($start <= $limit) {
        yield $start;
        $start += $step;
    }
}

foreach (xrange(1, 100, 2) as $num) {
    echo $num . PHP_EOL;
}

這段代碼所的出來的結果,和前面的那段代碼如出一轍,可是,它內部的原理是天翻地覆了。code

咱們剛纔說了,前面的代碼,range 會生成一個數組,而後 foreach 來迭代這個數組,從而取出某一個值。可是這段代碼呢,咱們從新定義了一個 xrange 函數,在函數中,咱們用了一個關鍵字 yield。咱們都知道定義一個函數,但願它返回一個值得時候,用 return 來返回。那麼這個 yield 呢,也能夠返回一個值,可是,它和 return 是大相徑庭的。協程

使用 yield 關鍵字,可讓函數在運行的時候,中斷,同時會保存整個函數的上下文,返回一個 Generator 類型的對象。在執行對象的 next 方法時,會從新加載中斷時的上下文,繼續運行,直到出現下一個 yield 爲止,若是後面沒有再出現 yield,那麼就認爲整個生成器結束了。對象

這樣,咱們上面的函數調用能夠等價地寫成這樣。內存

$nums = xrange(1, 100, 2);
while ($nums->valid()) {
    echo $nums->current() . "\n";
    $nums->next();
}

在這裏,$num 是一個 Generator 的對象。咱們在這裏看到三個方法,validcurrentnext。當咱們函數執行完了,後面沒有 yield 中斷了,那麼咱們在 xrange 函數就執行完了,那麼 valid 方法就會變成 false。而 current 呢,會返回當前 yield 後面的值,這是,生成器的函數會中斷。那麼在調用 next 方法以後,函數會繼續執行,直到下一個 yield 出現,或者函數結束。字符串

好了,到這裏,咱們看到了經過 yield 來「生成」一個值並返回。其實,yield 其實也能夠這麼寫 $ret = yield;。同返回值同樣,這裏是將一個值在繼續執行函數的時候,傳值進函數,能夠經過 Generator::send($value) 來使用。例如。it

function sum()
{
    $ret = yield;
    echo $ret . PHP_EOL;
}

$sum = sum();
$sum->send('I am from outside.');

這樣,程序就會打印出 send 方法傳進去的字符串了。在 yield 的兩邊能夠同時有調用。

function xrange($start, $limit, $step = 1) {
    while ($start <= $limit) {
        $ret = yield $start;
        $start += $step;
        echo $ret . PHP_EOL;
    }
}

$nums = xrange(1, 100, 2);
while ($nums->valid()) {
    echo $nums->current() . "\n";
    $nums->send($nums->current() + 1);
}

而像這樣的使用,send() 能夠返回下一個 yield 的返回。

其它的 Generator 方法

Generator::key()

對於 yield,咱們能夠這樣使用 yield $id => $value,這是,咱們能夠經過 key 方法來獲取 $id,而 current 方法返回的是 $value

Generator::rewind()

這個方法,能夠幫咱們讓生成器從新開始執行並保存上下文,同時呢,會返回第一個 yield 返回的內容。在第一次執行 send 方法的時候,rewind 會被隱式調用。

Generator::throw()

這個方法,向生成器中,拋送一個異常。

後記

yield 做爲 PHP 5.5 的新特性,讓咱們用了新的方法來高效地迭代數據。同時,咱們還可使用 yield 來實現協程。

相關文章
相關標籤/搜索