写在前面

工作中遇到一个很特别的写法

yield关键字

感觉以前从来没听过,稍微研究了一下。

文档

https://www.php.net/manual/zh/language.generators.syntax.php

yield生成器语法

对比returnyield的区别

function createRange($number = 0){
    $data = [];
    for($i=0;$i<$number;$i++){
        return $i;
    }
}

$result = createRange(10);
var_dump($result);

这段代码是我们常规的return执行,这里打印的结果,很正常,就是一个int类型的0

打印值是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文件读取的一个神器~

瑞思拜~

下班

最后修改:2025 年 07 月 25 日
如果您对各种技术博客文章感兴趣,欢迎关注拓行公众号,分享各种专业技术知识~