深入了解PHP中生成器yield的使用
如果是做Python或者其他語言的小伙伴,對(duì)于生成器應(yīng)該不陌生。但很多PHP開發(fā)者或許都不知道生成器這個(gè)功能,可能是因?yàn)樯善魇荘HP 5.5.0才引入的功能,也可以是生成器作用不是很明顯。但是,生成器功能的確非常有用。
1. 什么是 "yield"
生成器函數(shù)看上去就像一個(gè)普通函數(shù), 除了不是返回一個(gè)值之外, 生成器會(huì)根據(jù)需求產(chǎn)生更多的值。
看以下的例子:
function getValues() { yield 'value'; } // 輸出字符串 "value" echo getValues();
當(dāng)然, 這不是他生效的方式, 前面的例子會(huì)給你一個(gè)致命的錯(cuò)誤: 類生成器的對(duì)象不能被轉(zhuǎn)換成字符串
2.yield 解決的問題
解決運(yùn)行內(nèi)存的瓶頸,php程序中的變量存儲(chǔ)在內(nèi)存中,之前有遇到過讀取Excel文件時(shí)候,會(huì)出現(xiàn)內(nèi)存不足,出現(xiàn):
Fatal Error: Allowed memory size of xxxxxx bytes
所以會(huì)設(shè)置php 最大運(yùn)行內(nèi)存的設(shè)置: ini_set('memory_limit', '200M')
但是當(dāng)我們讀取5g 這么大的文件的時(shí)候,我們運(yùn)行內(nèi)存可能就吃不消了,所以會(huì)選擇yield
3."yield" & "return" 的區(qū)別
前面的錯(cuò)誤意味著 getValues()
方法不會(huì)如預(yù)期返回一個(gè)字符串,讓我們檢查一下他的類型:
function getValues() { return 'value'; } var_dump(getValues()); // string(5) "value" function getValues() { yield 'value'; } var_dump(getValues()); // class Generator#1 (0) {}
生成器 類實(shí)現(xiàn)了 生成器 接口, 這意味著你必須遍歷 getValue()
方法來取值:
foreach (getValues() as $value) { echo $value; } // 使用變量也是好的 $values = getValues(); foreach ($values as $value) { echo $value; }
但這不是唯一的不同!
一個(gè)生成器運(yùn)行你寫使用循環(huán)來迭代一維數(shù)組的代碼,而不需要在內(nèi)存中創(chuàng)建是一個(gè)數(shù)組,這可能會(huì)導(dǎo)致你超出內(nèi)存限制。
在下面的例子里我們創(chuàng)建一個(gè)有 800,000 元素的數(shù)字同時(shí)從 getValues()
方法中返回他,同時(shí)在此期間,我們將使用函數(shù) memory_get_usage() 來獲取分配給次腳本的內(nèi)存, 我們將會(huì)每增加 200,000 個(gè)元素來獲取一下內(nèi)存使用量,這意味著我們將會(huì)提出四個(gè)檢查點(diǎn):
<?php function getValues() { $valuesArray = []; // 獲取初始內(nèi)存使用量 echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL; for ($i = 1; $i < 800000; $i++) { $valuesArray[] = $i; // 為了讓我們能進(jìn)行分析,所以我們測量一下內(nèi)存使用量 if (($i % 200000) == 0) { // 來 MB 為單位獲取內(nèi)存使用量 echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB'. PHP_EOL; } } return $valuesArray; } $myValues = getValues(); // 一旦我們調(diào)用函數(shù)將會(huì)在這里創(chuàng)建數(shù)組 foreach ($myValues as $value) {}
前面例子發(fā)生的情況是這個(gè)腳本的內(nèi)存消耗和輸出:
0.34 MB
8.35 MB
16.35 MB
32.35 MB
這意味著我們的幾行腳本消耗了超過 30 MB
的內(nèi)存, 每次你你添加一個(gè)元素到 $valuesArray
數(shù)組中, 都會(huì)增加他在內(nèi)存中的大小。
讓我們使用 yield 同樣的例子:
<?php function getValues() { // 獲取內(nèi)存使用數(shù)據(jù) echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL; for ($i = 1; $i < 800000; $i++) { yield $i; // 做性能分析,因此可測量內(nèi)存使用率 if (($i % 200000) == 0) { // 內(nèi)存使用以 MB 為單位 echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB'. PHP_EOL; } } } $myValues = getValues(); // 在循環(huán)之前都不會(huì)有動(dòng)作 foreach ($myValues as $value) {} // 開始生成數(shù)據(jù)
這個(gè)腳本的輸出令人驚訝:
0.34 MB
0.34 MB
0.34 MB
0.34 MB
這不意味著你從 return 表達(dá)式遷移到 yield,但如果你在應(yīng)用中創(chuàng)建會(huì)導(dǎo)致服務(wù)器上內(nèi)存出問題的巨大數(shù)組,則 yield 更加適合你的情況。
4. 什么是 "yield" 選項(xiàng)
這里有很多 yield 的選項(xiàng), 我將強(qiáng)調(diào)他們中的幾個(gè):
a. 使用 yield, 你也可以使用 return。
function getValues() { yield 'value'; return 'returnValue'; } $values = getValues(); foreach ($values as $value) {} echo $values->getReturn(); // 'returnValue'
b. 返回鍵值對(duì):
function getValues() { yield 'key' => 'value'; } $values = getValues(); foreach ($values as $key => $value) { echo $key . ' => ' . $value; }
5. 生成器
1 使用生成器來生成一個(gè)1到100的數(shù)組
function my_range($start,$limit){ for($i=$start;$i<=$limit;$i++){ yield $i; } }
2 打印出來,看下返回究竟是什么
$arr = my_range(1,100); var_dump($arr);
結(jié)果是:
object(Generator)#1 (0) { }
可見是一個(gè)對(duì)象,是一個(gè)生成器對(duì)象,既然是對(duì)象那么也就是可以用foreach來遍歷
3 遍歷生成器
foreach($arr as $num){ echo $num.PHP_EOL; }
看到可以完整遍歷出來,那么與那樣實(shí)現(xiàn)的不同地方,意義在哪里呢。重點(diǎn)來了。
4 兩者內(nèi)存占用比較
上面已經(jīng)測試過使用數(shù)組的方式,隨著范圍的增大占用的內(nèi)存劇增,很快就超過了PHP的內(nèi)存上限。
那么使用生成器占用了多少內(nèi)存呢,來看看就知道了。
$start = memory_get_usage(); $arr = my_range(1, 100); $end = memory_get_usage(); echo $end - $start .' bytes'.PHP_EOL;
可以看到只占用了576bytes,當(dāng)然每個(gè)人測試的可能都會(huì)有點(diǎn)不同,環(huán)境不同,但是這不是重點(diǎn)。
我們?cè)賴L試增加數(shù)字范圍,可以看到數(shù)字范圍并沒有影響到內(nèi)存占用,也就是可以輕松的遍歷超大數(shù)字。
$start = memory_get_usage(); $arr = my_range(1, 100000000); $end = memory_get_usage(); echo $end - $start .' bytes'.PHP_EOL; foreach($arr as $num){ echo $num.PHP_EOL; }
這下我們就可以遍歷1到10000000的數(shù)字了,不相信內(nèi)存占用那么低的小伙伴,可以打開任務(wù)管理器毫無波瀾,即時(shí)再上調(diào)數(shù)字范圍。
5、生成器遍歷原理
生成器既然這么強(qiáng)大,那么他的遍歷原理是什么呢。使用foreach遍歷的時(shí)候,相當(dāng)于生成器執(zhí)行了以下操作。
while($arr->valid()){ echo $arr->current().PHP_EOL; $arr->next(); } //$arr->valid() 判斷生成器是否關(guān)閉 //$arr->current() 返回當(dāng)前對(duì)象 //$arr->next() 繼續(xù)往下執(zhí)行生成器
到此這篇關(guān)于深入了解PHP中生成器yield的使用的文章就介紹到這了,更多相關(guān)PHP生成器yield內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
PHP、Nginx、Apache中禁止網(wǎng)頁被iframe引用的方法
這篇文章主要介紹了PHP、Nginx、Apache中禁止網(wǎng)頁被iframe引用的方法,使用X-Frame-Options實(shí)現(xiàn),需要的朋友可以參考下2014-06-06PHP超級(jí)全局變量【$GLOBALS,$_SERVER,$_REQUEST等】用法實(shí)例分析
這篇文章主要介紹了PHP超級(jí)全局變量用法,結(jié)合實(shí)例形式分析了PHP中$GLOBALS,$_SERVER,$_REQUEST等超級(jí)全局變量相關(guān)概念、功能、使用方法及操作注意事項(xiàng),需要的朋友可以參考下2019-12-12mayfish 數(shù)據(jù)入庫驗(yàn)證代碼
mayfish 可以靈活的自定義將要執(zhí)行寫入的數(shù)據(jù)內(nèi)容的校驗(yàn)規(guī)則,以減少開發(fā)人員手動(dòng)對(duì)每一個(gè)字段的數(shù)據(jù)進(jìn)行校驗(yàn)的麻煩。2010-04-04PHP實(shí)現(xiàn)通過正則表達(dá)式替換回調(diào)的內(nèi)容標(biāo)簽
這篇文章主要介紹了PHP實(shí)現(xiàn)通過正則表達(dá)式替換回調(diào)的內(nèi)容標(biāo)簽的方法,涉及php正則匹配與替換的相關(guān)技巧,需要的朋友可以參考下2015-06-06php使用socket post數(shù)據(jù)到其它web服務(wù)器的方法
這篇文章主要介紹了php使用socket post數(shù)據(jù)到其它web服務(wù)器的方法,涉及php使用socket傳輸數(shù)據(jù)的相關(guān)技巧,需要的朋友可以參考下2015-06-06