學習PHP生成器的使用
什麼是生成器?
聽著高大上的名字,感覺像是創造東西的功能,實際上,生成器是一個用於迭代的迭代器。它提供了一種更容易的方式來實現簡單的物件迭代,相比較定義類別實作Iterator介面的方式,效能開銷和複雜性大大降低。
推薦:《PHP影片教學》
說了半天不如直接看看程式碼更直覺。
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/學習PHP產生器的使用.php
#參考文件: https://www.php.net/manual/zh/language.generators.overview.php https://www.php.net/manual/zh/class.generator.php
######以上是帶你詳解PHP生成器的使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!