目錄
- 什么是生成器?
- 生成器有什么用?
- 生成器的應用
- 返回空值以及中斷
- 返回鍵值對形式
- 外部傳遞數據
- yield from 語法
- 小驚喜
- 生成器可以用count獲取數量嗎?
- 使用生產器來獲取斐波那契數列
- 總結
什么是生成器?
聽著高大上的名字,感覺像是創(chuàng)造什么東西的一個功能,實際上,生成器是一個用于迭代的迭代器。它提供了一種更容易的方式來實現簡單的對象迭代,相比較定義類實現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 ;最后,外部循環(huán)時,一次循環(huán)取一個 yield 的返回值。在這個例子,循環(huán)三次返回了1、2、3這三個數字。然后在循環(huán)外部又寫了兩行 yield 分別輸出了1000和1001。因此,外部的 foreach 一共循環(huán)輸出了五次。
很神奇吧,明明是一個方法,為什么能夠循環(huán)它而且還是很奇怪的一種返回循環(huán)體的格式。我們直接打印這個 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
}
生成器有什么用?
搞了半天不就是個迭代器嘛?搞這么麻煩干嘛,直接用迭代器或者在方法中直接返回一個數組不就好了嗎?沒錯,正常情況下真的沒有這么麻煩,但是如果是在數據量特別大的情況下,這個生成器就能發(fā)揮它的強大威力了。生成器最最強大的部分就在于,它不需要一個數組或者任何的數據結構來保存這一系列數據。每次迭代都是代碼執(zhí)行到 yield 時動態(tài)返回的。因此,生成器能夠極大的節(jié)約內存。
// 內存占用測試
$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 個循環(huán)后獲取結果,不過也可以直觀地看出。使用生成器的版本僅僅消耗了 2M 的內存,而未使用生成器的版本則消耗了 35M 的內存,直接已經10多倍的差距了,而且越大的量差距超明顯。因此,有大神將生成器說成是PHP中最被低估了的一個特性。
生成器的應用
接下來我們來看看生成器的一些基本的應用方式。
返回空值以及中斷
生成器當然也可以返回空值,直接 yield; 不帶任何值就可以返回一個空值了。而在方法中直接使用 return; 也可以用來中斷生成器的繼續(xù)執(zhí)行。下面的代碼我們在 \i = 4; 的時候返回的是個空值,也就是不會輸出 5 (因為我們返回的是i=4;的時候返回的是個空值,也就是不會輸出5(因為我們返回的是i + 1 )。然后在 $i == 7 的時候使用 return; 中斷生成器的繼續(xù)執(zhí)行,也就是循環(huán)最多只會輸出到 7 就結束了。
// 返回空值以及中斷
function test4()
{
for ($i = 0; $i 10; $i++) {
if ($i == 4) {
yield; // 返回null值
}
if ($i == 7) {
return; // 中斷生成器執(zhí)行
}
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 的返回值。然后我們根據這個值可以做一些判斷,比如根據外部條件中斷生成器的執(zhí)行。
function test6()
{
for ($i = 0; $i 10; $i++) {
// 正常獲取循環(huán)值,當外部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
上述代碼理解起來可能比較繞,但是注意記住注釋的那行話就行了(正常獲取循環(huán)值,當外部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中的一個隱藏的寶藏,不僅是對于內存節(jié)約來說,而且語法其實也非常的簡潔明了。我們不需要在方法內部再多定義一個數組去存儲返回值,直接 yield 一項一項的返回就可以了。在實際的項目中完全值得嘗試一把,但是嘗試完了別忘了和小伙伴們分享,大部分人可能真的沒有接觸過這個特性哦??!
測試代碼: github.com/zhangyue050…
以上就是PHP 生成器的使用詳解的詳細內容,更多關于PHP 生成器的資料請關注腳本之家其它相關文章!
您可能感興趣的文章:- PHP Web表單生成器案例分析
- PHP迭代器和生成器用法實例分析
- PHP生成器(generator)和協(xié)程的實現方法詳解
- PHP生成器功能與用法實例分析
- PHP中你可能忽略的性能優(yōu)化利器:生成器
- PHP十六進制顏色隨機生成器功能示例
- PHP新特性詳解之命名空間、性狀與生成器
- php驗證碼生成器
- thinkPHP連接sqlite3數據庫的實現方法(附Thinkphp代碼生成器下載)
- PHP5.5迭代生成器用法實例詳解