若是是作Python或者其餘語言的小夥伴,對於生成器應該不陌生。但不少PHP開發者或許都不知道生成器這個功能,多是由於生成器是PHP 5.5.0才引入的功能,也能夠是生成器做用不是很明顯。可是,生成器功能的確很是有用。php
優勢
直接講概念估計你聽完仍是一頭霧水,因此咱們先來講說優勢,也許能勾起你的興趣。那麼生成器有哪些優勢,以下:html
- 生成器會對PHP應用的性能有很是大的影響
- PHP代碼運行時節省大量的內存
- 比較適合計算大量的數據
那麼,這些神奇的功能到底是如何作到的?咱們先來舉個例子。segmentfault
概念引入
首先,放下生成器概念的包袱,來看一個簡單的PHP函數:數組
function createRange($number){
$data = []; for($i=0;$i<$number;$i++){ $data[] = time(); } return $data; }
這是一個很是常見的PHP函數,咱們在處理一些數組的時候常常會使用。這裏的代碼也很是簡單:瀏覽器
- 咱們建立一個函數。
- 函數內包含一個 for 循環,咱們循環的把當前時間放到
$data
裏面 for
循環執行完畢,把 $data 返回出去。
下面沒完,咱們繼續。咱們再寫一個函數,把這個函數的返回值循環打印出來:函數
$result = createRange(10); // 這裏調用上面咱們建立的函數 foreach($result as $value){ sleep(1);//這裏停頓1秒,咱們後續有用 echo $value.'<br />'; }
咱們在瀏覽器裏面看一下運行結果:post
這裏很是完美,沒有任何問題。(固然 sleep(1) 效果大家看不出來)性能
思考一個問題
咱們注意到,在調用函數 createRange 的時候給 $number 的傳值是10,一個很小的數字。假設,如今傳遞一個值10000000
(1000萬)。spa
那麼,在函數 createRange 裏面,for
循環就須要執行1000
萬次。且有1000
萬個值被放到 $data 裏面,而$data
數組在是被放在內存內。因此,在調用函數時候會佔用大量內存。.net
這裏,生成器就能夠大顯身手了。
建立生成器
咱們直接修改代碼,大家注意觀察:
function createRange($number){ for($i=0;$i<$number;$i++){ yield time(); } }
看下這段和剛剛很像的代碼,咱們刪除了數組 $data ,並且也沒有返回任何內容,而是在 time() 以前使用了一個關鍵字yield。
使用生成器
咱們再運行一下第二段代碼:
$result = createRange(10); // 這裏調用上面咱們建立的函數 foreach($result as $value){ sleep(1); echo $value.'<br />'; }
咱們奇蹟般的發現了,輸出的值和第一次沒有使用生成器的不同。這裏的值(時間戳)中間間隔了1秒。
這裏的間隔一秒其實就是 sleep(1) 形成的後果。可是爲何第一次沒有間隔?那是由於:
- 未使用生成器時: createRange 函數內的 for 循環結果被很快放到 $data 中,而且當即返回。因此, foreach 循環的是一個固定的數組。
- 使用生成器時: createRange 的值不是一次性快速生成,而是依賴於 foreach 循環。 foreach 循環一次, for 執行一次。
到這裏,你應該對生成器有點兒頭緒。
深刻理解生成器
代碼剖析
下面咱們來對於剛剛的代碼進行剖析。
function createRange($number){ for($i=0;$i<$number;$i++){ yield time(); } } $result = createRange(10); // 這裏調用上面咱們建立的函數 foreach($result as $value){ sleep(1); echo $value.'<br />'; }
咱們來還原一下代碼執行過程。
- 首先調用 createRange 函數,傳入參數
10
,可是 for 值執行了一次而後中止了,而且告訴 foreach 第一次循環能夠用的值。 - foreach 開始對 $result 循環,進來首先 sleep(1) ,而後開始使用 for 給的一個值執行輸出。
- foreach 準備第二次循環,開始第二次循環以前,它向 for 循環又請求了一次。
- for 循環因而又執行了一次,將生成的時間戳告訴 foreach .
- foreach 拿到第二個值,而且輸出。因爲 foreach 中 sleep(1) ,因此, for 循環延遲了1秒生成當前時間
因此,整個代碼執行中,始終只有一個記錄值參與循環,內存中也只有一條信息。
不管開始傳入的 $number 有多大,因爲並不會當即生成全部結果集,因此內存始終是一條循環的值。
概念理解
到這裏,你應該已經大概理解什麼是生成器了。下面咱們來講下生成器原理。
首先明確一個概念:生成器yield關鍵字不是返回值,他的專業術語叫產出值,只是生成一個值
那麼代碼中 foreach 循環的是什麼?實際上是PHP在使用生成器的時候,會返回一個 Generator 類的對象。 foreach 能夠對該對象進行迭代,每一次迭代,PHP會經過 Generator 實例計算出下一次須要迭代的值。這樣 foreach 就知道下一次須要迭代的值了。
並且,在運行中 for 循環執行後,會當即中止。等待 foreach 下次循環時候再次和 for 索要下次的值的時候,循環纔會再執行一次,而後當即再次中止。直到不知足條件不執行結束。
實際開發應用
不少PHP開發者不瞭解生成器,其實主要是不瞭解應用領域。那麼,生成器在實際開發中有哪些應用?
讀取超大文件
PHP開發不少時候都要讀取大文件,好比csv文件、text文件,或者一些日誌文件。這些文件若是很大,好比5個G。這時,直接一次性把全部的內容讀取到內存中計算不太現實。
這裏生成器就能夠派上用場啦。簡單看個例子:讀取text文件
咱們建立一個text文本文檔,並在其中輸入幾行文字,示範讀取。
<?php
header("content-type:text/html;charset=utf-8"); function readTxt() { # code... $handle = fopen("./test.txt", 'rb'); while (feof($handle)===false) { # code... yield fgets($handle); } fclose($handle); } foreach (readTxt() as $key => $value) { # code... echo $value.'<br />'; }
經過上圖的輸出結果咱們能夠看出代碼徹底正常。
可是,背後的代碼執行規則卻一點兒也不同。使用生成器讀取文件,第一次讀取了第一行,第二次讀取了第二行,以此類推,每次被加載到內存中的文字只有一行,大大的減少了內存的使用。
這樣,即便讀取上G的文本也不用擔憂,徹底能夠像讀取很小文件同樣編寫代碼。
百萬級別的訪問量
yield生成器是php5.5以後出現的,yield提供了一種更容易的方法來實現簡單的迭代對象,相比較定義類實現 Iterator 接口的方式,性能開銷和複雜性大大下降。
yield生成器容許你 在 foreach 代碼塊中寫代碼來迭代一組數據而不須要在內存中建立一個數組。
使用示例:
- /**
- * 計算平方數列
- * @param $start
- * @param $stop
- * @return Generator
- */
- function squares($start, $stop) {
- if ($start < $stop) {
- for ($i = $start; $i <= $stop; $i++) {
- yield $i => $i * $i;
- }
- }
- else {
- for ($i = $start; $i >= $stop; $i--) {
- yield $i => $i * $i; //迭代生成數組: 鍵=》值
- }
- }
- }
- foreach (squares(3, 15) as $n => $square) {
- echo $n . ‘squared is‘ . $square . ‘<br>‘;
- }
- 輸出:
- 3 squared is 9
- 4 squared is 16
- 5 squared is 25
- ...
示例2:
- /對某一數組進行加權處理
- $numbers = array(‘nike‘ => 200, ‘jordan‘ => 500, ‘adiads‘ => 800);
- //一般方法,若是是百萬級別的訪問量,這種方法會佔用極大內存
- function rand_weight($numbers)
- {
- $total = 0;
- foreach ($numbers as $number => $weight) {
- $total += $weight;
- $distribution[$number] = $total;
- }
- $rand = mt_rand(0, $total-1);
- foreach ($distribution as $num => $weight) {
- if ($rand < $weight) return $num;
- }
- }
- //改用yield生成器
- function mt_rand_weight($numbers) {
- $total = 0;
- foreach ($numbers as $number => $weight) {
- $total += $weight;
- yield $number => $total;
- }
- }
- function mt_rand_generator($numbers)
- {
- $total = array_sum($numbers);
- $rand = mt_rand(0, $total -1);
- foreach (mt_rand_weight($numbers) as $num => $weight) {
- if ($rand < $weight) return $num;
- }
- }
本文轉載自https://segmentfault.com/a/1190000012334856 http://blog.csdn.net/moliyiran/article/details/72835896