写在前面
工作中遇到一个很特别的写法
yield
关键字
感觉以前从来没听过,稍微研究了一下。
文档
https://www.php.net/manual/zh/language.generators.syntax.php
对比return
和yield
的区别
function createRange($number = 0){
$data = [];
for($i=0;$i<$number;$i++){
return $i;
}
}
$result = createRange(10);
var_dump($result);
这段代码是我们常规的return
执行,这里打印的结果,很正常,就是一个int
类型的0
也就是return
这条语句,直接结束了整个函数的执行,自然,循环也就只循环了一次。
但是我们将return
换成yield
关键字的话,返回的内容就变成了一个对象
function createRange($number = 0){
$data = [];
for($i=0;$i<$number;$i++){
yield $i;
}
}
$result = createRange(10);
var_dump($result);
按照官方的说法,这是个生成器对象
yield
的优势
那么,我们再改一下,看看yield
的独特魅力
function createRange($number = 0){
$data = [];
for($i=0;$i<$number;$i++){
// 耗时操作
sleep(2);
yield '第' . ($i+1) . '次循环,模拟耗时操作ing……' . PHP_EOL;
}
}
$result = createRange(10);
foreach ($result as $value) {
echo $value;
}
这次我们没有直接打印var_dump()
而是用了一个循环的方式,迭代这个生成器对象,并且echo
这个内容
然后发现本该在第10行
已经生成好的数组,并没有完全生成好
在执行createRange
后迭代的每次循环当中,才去执行第5行
的模拟耗时代码
执行当中可以发现,所有的输出并不是一股脑的输出出来的,而是一条一条执行出来的
大文件读写
原先读取的方式
这种方式就是将整个文件都读取出来,然后逐行处理
function readLargeFile($filePath) {
if (!file_exists($filePath)) {
throw new InvalidArgumentException("文件不存在: $filePath");
}
$file = fopen($filePath, 'r');
if (!$file) {
throw new RuntimeException("无法打开文件: $filePath");
}
// 逐行读取并组合数据返回
$data = [];
while (($line = fgets($file)) !== false) {
$data[] = $line;
}
fclose($file);
return $data;
}
// 使用生成器处理大文件
try {
$data = readLargeFile('large_data.txt');
foreach ($data as $lineNumber => $line) {
// 处理每一行数据(例如:解析、统计或写入其他文件)
echo "Line " . ($lineNumber + 1) . ": " . substr($line, 0, 50) . "..." . PHP_EOL;
}
} catch (Exception $e) {
echo "错误: " . $e->getMessage();
}
使用yield
关键字改造优化
function readLargeFile($filePath) {
if (!file_exists($filePath)) {
throw new InvalidArgumentException("文件不存在: $filePath");
}
$file = fopen($filePath, 'r');
if (!$file) {
throw new RuntimeException("无法打开文件: $filePath");
}
// 逐行读取并通过 yield 返回
while (($line = fgets($file)) !== false) {
yield $line;
}
fclose($file);
}
// 使用生成器处理大文件
try {
foreach (readLargeFile('large_data.txt') as $lineNumber => $line) {
// 处理每一行数据(例如:解析、统计或写入其他文件)
echo "Line " . ($lineNumber + 1) . ": " . substr($line, 0, 50) . "..." . PHP_EOL;
}
} catch (Exception $e) {
echo "错误: " . $e->getMessage();
}
特点
这里的方式就比较好,因为数据是一行一行进行读取的
内存优化:无论文件多大,内存使用始终保持在较低水平(仅存储当前行)。
延迟处理:只有在迭代到某一行时才会读取和处理它。
异常安全:使用 try-catch
确保文件资源被正确关闭。
csv
超大文件读取
这里附上代码
function parseLargeCsv($filePath) {
$file = fopen($filePath, 'r');
if (!$file) {
throw new RuntimeException("无法打开 CSV 文件: $filePath");
}
// 跳过 CSV 表头(如果有)
$header = fgetcsv($file);
while (($data = fgetcsv($file)) !== false) {
// 将数据行与表头关联,生成关联数组
yield array_combine($header, $data);
}
fclose($file);
}
// 使用示例:处理大型 CSV 文件
foreach (parseLargeCsv('large_data.csv') as $row) {
echo "用户: {$row['name']}, 邮箱: {$row['email']}" . PHP_EOL;
}
可以有效防止出现这种报错:
Allowed memory size of XXX bytes exhausted
顺便推荐一下csv
文件读取的一个神器~
瑞思拜~
下班