PHP版本>=5.5
原理:yield關鍵字會生成一個Generator類的對象,PHP通過Generator實例計算出下一次迭代的值,再次返回一個Generator對象并停止循環(即循環一次執行一次)。
理解:使用在for/foreach/while循環內部,用來返回循環結構體本次生成的元素。yield會記錄結構體本次循環所處的位置,下次執行循環時從上次的位置開始執行,再次生成一個元素。所以yield返回的數組內永遠只有一個元素,所以叫做生成器。
目的:節省內存,防止內存溢出。
yield節省內存場景:
(1)在for/foreach/while循環結構體中,生成上億個元素,在內存中都只有一個元素。
(2)在fgets讀取文件中,每次都讀取一行數據到內存中,大文件可以像小文件一樣讀取。
(3)在mysql讀取數據中,每次都獲取一行數據到內存中,就算幾十萬的數據也不會占用過多內存。
場景(1):
<?php
/*** 使用 yield關鍵字創建數組* @param $number* @return Generator*/
function createRangeYield($number): Generator
{for ($i = 0; $i <= $number; $i++) {yield time();if (($i % 20000) == 0) {// 內存使用以 MB 為單位echo "$i\t" . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;}}
}/*** 直接生成數組* @param $number* @return array*/
function createRange($number): array
{$data = [];for ($i = 0; $i <= $number; $i++) {$data[] = time();if (($i % 20000) == 0) {// 內存使用以 MB 為單位echo "$i\t" . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;}}return $data;
}$time1 = microtime(true) * 1000;
echo "常規函數循環 10W次內存使用量\n";
$result = createRange(100000); // 這里調用上面創建的函數
foreach ($result as $value) {# code...
}
$time2 = microtime(true) * 1000;
echo "耗時:".( round($time2 - $time1,3)). "毫秒\n";$time1 = microtime(true) * 1000;
echo "\nyield函數循環 10W次內存使用量\n";
$result = createRangeYield(100000); // 這里調用上面創建的函數
foreach ($result as $value) {# code...
}
$time2 = microtime(true) * 1000;
echo "耗時:".( round($time2 - $time1,3)). "毫秒\n";
執行結果分析:
場景(2):
<?php
header("content-type:text/html;charset=utf-8");
/*** 使用 yield關鍵字讀取大文件* @return Generator*/
function readTxtYield(): Generator
{$handle = fopen("D:/ttt.txt", 'rb');$i = 0;while (feof($handle) === false) {yield fgets($handle);if (($i % 200000) == 0) {// 內存使用以 MB 為單位echo "$i\t" . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;}$i++;}fclose($handle);
}/*** 常規函數讀取大文件* @return array*/
function readTxt(): array
{$handle = fopen("D:/ttt.txt", 'rb');$i = 0;$lines = [];while (feof($handle) === false) {$lines[] = fgets($handle);if (($i % 200000) == 0) {// 內存使用以 MB 為單位echo "$i\t" . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;}$i++;}fclose($handle);return $lines;
}$time1 = microtime(true) * 1000;
echo "常規函數讀取 100W行文件內存使用量\n";
foreach (readTxt() as $key => $value) {# code...
}
$time2 = microtime(true) * 1000;
echo "耗時:".( round($time2 - $time1,3)). "毫秒\n";$time1 = microtime(true) * 1000;
echo "\nyield函數讀取 100W行文件內存使用量\n";
foreach (readTxtYield() as $key => $value) {# code...
}
$time2 = microtime(true) * 1000;
echo "耗時:".( round($time2 - $time1,3)). "毫秒\n";
執行結果分析:
場景(3):
<?phpconst DB_NAME = 'yibai_purchase';// 數據庫名稱
const DB_HOST = '192.168.73.73';
const DB_USERNAME = 'dev_purchase';
const DB_PASSWORD = 'yb123456';
const LIMIT = 50;
const DSN = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME;/*** 使用 yield關鍵字 讀取數據庫數據* @return Generator*/
function readSqlDataYield(): Generator
{$i = 0;$lines = [];$options = [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,];$connectObj = new PDO(DSN, DB_USERNAME, DB_PASSWORD, $options);$query_tables = "SELECT * FROM yibai_purchase.pur_supplier_payment_record WHERE 1=1 limit 100000";$stmt = $connectObj->query($query_tables);while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {yield $row;if (($i % 20000) == 0) {// 內存使用以 MB 為單位echo "$i\t" . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;}$i++;}
}/*** 常規函數讀取數據庫數據* @return array*/
function readSqlData(): array
{$i = 0;$lines = [];$options = [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,];$connectObj = new PDO(DSN, DB_USERNAME, DB_PASSWORD, $options);$query_tables = "SELECT * FROM yibai_purchase.pur_supplier_payment_record WHERE 1=1 limit 100000";$stmt = $connectObj->query($query_tables);while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {$lines[] = $row;if (($i % 20000) == 0) {// 內存使用以 MB 為單位echo "$i\t" . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;}$i++;}return $lines;}$time1 = microtime(true) * 1000;
echo "常規函數讀取 10W行數據庫數據內存使用量\n";
foreach (readSqlData() as $key => $value) {# code...
}
$time2 = microtime(true) * 1000;
echo "耗時:".( round($time2 - $time1,3)). "毫秒\n";$time1 = microtime(true) * 1000;
echo "\nyield函數讀取 10W行數據庫數據內存使用量\n";
foreach (readSqlDataYield() as $key => $value) {# code...
}
$time2 = microtime(true) * 1000;
echo "耗時:".( round($time2 - $time1,3)). "毫秒\n";
執行結果分析:
綜合三種場景,發現內存使用量明顯減少,并且執行用時不會有太大差異。