農林漁牧網

您現在的位置是:首頁 > 農業

學習PHP生成器的使用

2022-01-09由 碼農老張Zy 發表于 農業

生成器是什麼意思

什麼是生成器?

聽著高大上的名字,感覺像是創造什麼東西的一個功能,實際上,生成器是一個用於迭代的迭代器。它提供了一種更容易的方式來實現簡單的物件迭代,相比較定義類實現Iterator介面的方式,效能開銷和複雜性大大降低。

說了半天不如直接看看程式碼更直觀。

function test1(){    for ($i = 0; $i < 3; $i++) {        yield $i + 1;    }    yield 1000;    yield 1001;}foreach (test1() as $t) {    echo $t, PHP_EOL;}// 1// 2// 3// 1000// 1001

就是這麼簡單的一段程式碼。首先,生成器必須在方法中並使用 yield 關鍵字;其次,每一個 yield 可以看作是一次 return ;最後,外部迴圈時,一次迴圈取一個 yield 的返回值。在這個例子,迴圈三次返回了1、2、3這三個數字。然後在迴圈外部又寫了兩行 yield 分別輸出了1000和1001。因此,外部的 foreach 一共迴圈輸出了五次。

很神奇吧,明明是一個方法,為什麼能夠迴圈它而且還是很奇怪的一種返回迴圈體的格式。我們直接列印這個 test() 方法看看列印的是什麼:

// 是一個生成器物件var_dump(test1());// Generator Object// (// )

當使用了 yield 進行內容返回後,返回的是一個 Generator 物件。這個物件就叫作生成器物件,它不能直接被 new 例項化,只能透過生成器函式這種方式返回。這個類包含 current() 、 key() 等方法,而且最主要的這個類實現了 Iterator 介面,所以,它就是一個特殊的迭代器類。

Generator implements Iterator {    /* 方法 */    public current ( void ) : mixed    public key ( void ) : mixed    public next ( void ) : void    public rewind ( void ) : void    public send ( mixed $value ) : mixed    public throw ( Exception $exception ) : void    public valid ( void ) : bool    public __wakeup ( void ) : void}

生成器有什麼用?

搞了半天不就是個迭代器嘛?搞這麼麻煩幹嘛,直接用迭代器或者在方法中直接返回一個數組不就好了嗎?沒錯,正常情況下真的沒有這麼麻煩,但是如果是在資料量特別大的情況下,這個生成器就能發揮它的強大威力了。生成器最最強大的部分就在於,它不需要一個數組或者任何的資料結構來儲存這一系列資料。每次迭代都是程式碼執行到 yield 時動態返回的。因此,生成器能夠極大的節約記憶體。

// 記憶體佔用測試$start_time = microtime(true);function test2($clear = false){    $arr = [];    if($clear){        $arr = null;        return;    }    for ($i = 0; $i < 1000000; $i++) {        $arr[] = $i + 1;    }    return $arr;}$array = test2();foreach ($array as $val) {}$end_time = microtime(true);echo “time: ”, bcsub($end_time, $start_time, 4), PHP_EOL;echo “memory (byte): ”, memory_get_usage(true), PHP_EOL;// time: 0。0513// memory (byte): 35655680$start_time = microtime(true);function test3(){    for ($i = 0; $i < 1000000; $i++) {        yield $i + 1;    }}$array = test3();foreach ($array as $val) {}$end_time = microtime(true);echo “time: ”, bcsub($end_time, $start_time, 4), PHP_EOL;echo “memory (byte): ”, memory_get_usage(true), PHP_EOL;// time: 0。0517// memory (byte): 2097152

上述程式碼只是簡單的進行 1000000 個迴圈後獲取結果,不過也可以直觀地看出。使用生成器的版本僅僅消耗了 2M 的記憶體,而未使用生成器的版本則消耗了 35M 的記憶體,直接已經10多倍的差距了,而且越大的量差距超明顯。因此,有大神將生成器說成是PHP中最被低估了的一個特性。

生成器的應用

接下來我們來看看生成器的一些基本的應用方式。

返回空值以及中斷

生成器當然也可以返回空值,直接 yield; 不帶任何值就可以返回一個空值了。而在方法中直接使用 return; 也可以用來中斷生成器的繼續執行。下面的程式碼我們在 \$i = 4; 的時候返回的是個空值,也就是不會輸出 5 (因為我們返回的是 $i + 1 )。然後在 $i == 7 的時候使用 return; 中斷生成器的繼續執行,也就是迴圈最多隻會輸出到 7 就結束了。

// 返回空值以及中斷function test4(){    for ($i = 0; $i < 10; $i++) {        if ($i == 4) {            yield; // 返回null值        }        if ($i == 7) {            return; // 中斷生成器執行        }        yield $i + 1;    }}foreach (test4() as $t) {    echo $t, PHP_EOL;}// 1// 2// 3// 4// 5// 6// 7

返回鍵值對形式

不要驚訝,生成器真的是可以返回鍵值對形式的可遍歷物件供 foreach 使用的,而且語法非常好記: yield key => value; 是不是和陣列項的定義形式一模一樣,非常直觀好理解。

function test5(){    for ($i = 0; $i < 10; $i++) {        yield ‘key。’ 。 $i => $i + 1;    }}foreach (test5() as $k=>$t) {    echo $k 。 ‘:’ 。 $t, PHP_EOL;}// key。0:1// key。1:2// key。2:3// key。3:4// key。4:5// key。5:6// key。6:7// key。7:8// key。8:9// key。9:10

外部傳遞資料

我們可以透過 Generator::send 方法來向生成器中傳入一個值。傳入的這個值將會被當做生成器當前 yield 的返回值。然後我們根據這個值可以做一些判斷,比如根據外部條件中斷生成器的執行。

function test6(){    for ($i = 0; $i < 10; $i++) {        // 正常獲取迴圈值,當外部send過來值後,yield獲取到的就是外部傳來的值了        $data = (yield $i + 1);        if($data == ‘stop’){            return;        }    }}$t6 = test6();foreach($t6 as $t){    if($t == 3){        $t6->send(‘stop’);    }    echo $t, PHP_EOL;}// 1// 2// 3

上述程式碼理解起來可能比較繞,但是注意記住註釋的那行話就行了(正常獲取迴圈值,當外部send過來值後,yield獲取到的就是外部傳來的值了)。另外,變數獲取 yield 的值,必須要用括號括起來。

yield from 語法

yield from 語法其實就是指的從另一個可迭代物件中一個一個的獲取資料並形成生成器返回。直接看程式碼。

function test7(){    yield from [1, 2, 3, 4];    yield from new ArrayIterator([5, 6]);    yield from test1();}foreach (test7() as $t) {    echo ‘test7:’, $t, PHP_EOL;}// test7:1// test7:2// test7:3// test7:4// test7:5// test7:6// test7:1// test7:2// test7:3// test7:1000

在 test7() 方法中,我們使用 yield from 分別從普通陣列、迭代器物件、另一個生成器中獲取資料並做為當前生成器的內容進行返回。

小驚喜

生成器可以用count獲取數量嗎?

抱歉,生成器是不能用count來獲取它的數量的。

$c = count(test1()); // Warning: count(): Parameter must be an array or an object that implements Countable// echo $c, PHP_EOL;

使用 count 來獲取生成器的數量將直接報 Warning 警告。直接輸出將會一直顯示是 1 ,因為 count 的特性(強制轉換成陣列都會顯示 1 )。

使用生產器來獲取斐波那契數列

// 利用生成器生成斐波那契數列function fibonacci($item){    $a = 0;    $b = 1;    for ($i = 0; $i < $item; $i++) {        yield $a;        $a = $b - $a;        $b = $a + $b;    }}$fibo = fibonacci(10);foreach ($fibo as $value) {    echo “$value\n”;}

這段程式碼就不多解釋了,非常直觀的一段程式碼了。

總結

生成器絕對是PHP中的一個隱藏的寶藏,不僅是對於記憶體節約來說,而且語法其實也非常的簡潔明瞭。我們不需要在方法內部再多定義一個數組去儲存返回值,直接 yield 一項一項的返回就可以了。在實際的專案中完全值得嘗試一把,但是嘗試完了別忘了和小夥伴們分享,大部分人可能真的沒有接觸過這個特性哦!!

測試程式碼: https://github。com/zhangyue0503/dev-blog/blob/master/php/202002/source/%E5%AD%A6%E4%B9%A0PHP%E7%94%9F%E6%88%90%E5%99%A8%E7%9A%84%E4%BD%BF%E7%94%A8。php

參考文件: https://www。php。net/manual/zh/language。generators。overview。php

https://www。php。net/manual/zh/class。generator。php