php使用yield读取百万级别的excel文件
前言
我们读取百万级excel文件时,常常都要设置ini_set('memory_limit', '200M')
,非常耗内存,这时候就引进了yield,代码运行时节省大量的内存
一、什么是yield?
生成器函数看起来像普通函数——不同的是普通函数返回一个值,而生成器可以 yield 生成多个想要的值。 任何包含 yield 的函数都是一个生成器函数。
当一个生成器被调用的时候,它返回一个可以被遍历的对象.当你遍历这个对象的时候(例如通过一个foreach循环),PHP 将会在每次需要值的时候调用对象的遍历方法,并在产生一个值之后保存生成器的状态,这样它就可以在需要产生下一个值的时候恢复调用状态。
一旦不再需要产生更多的值,生成器可以简单退出,而调用生成器的代码还可以继续执行,就像一个数组已经被遍历完了。
生成器函数的核心是yield关键字。它最简单的调用形式看起来像一个return申明,不同之处在于普通return会返回值并终止函数的执行,而yield会返回一个值给循环调用此生成器的代码并且只是暂停执行生成器函数。
示例 #1 一个简单的生成值的例子
<?php
function gen_one_to_three() {
for ($i = 1; $i <= 3; $i++) {
//注意变量$i的值在不同的yield之间是保持传递的。
yield $i;
}
}
$generator = gen_one_to_three();
foreach ($generator as $value) {
echo "$value\n";
}
?>
以上例程会输出:
1
2
3
详细内容,可见php官方文档:https://www.php.net/manual/zh/language.generators.syntax.php
二、使用
1.首先读取excel文件
我这里读取excel文件是使用PhpSpreadsheet,composer require phpoffice/phpspreadsheet
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xls');
$reader->setReadDataOnly(TRUE);
$spreadsheet = $reader->load('test.xls'); //载入excel表格
$worksheet = $spreadsheet->getActiveSheet();
$highestRow = $worksheet->getHighestRow();
$lines = $highestRow - 1;
if ($lines <= 0) {
echo 'Excel表格中没有数据';
die;
}
//获取excel内容
$data = self::yieldData($highestRow, $worksheet);
//开始使用
foreach ($data as $k => $v) {
//使用的时候就会读取一条出来,就不会把所有数据读取放在$data里面,造成内存不足,这就是yield的强大之处
var_dump($v);
}
2.读取数据
代码如下(示例):
private static function yieldData($highestRow, $worksheet)
{
for ($row = 2; $row <= $highestRow; ++$row) {
$data[$row]['name'] = $worksheet->getCellByColumnAndRow(1, $row)->getValue();
$data[$row]['remark'] = $worksheet->getCellByColumnAndRow(2, $row)->getValue();
yield $data[$row];
}
}
总结
网上也有文章让大家把yield理解为暂时停止函数的执行,等待外部的激活从而再次执行。虽然看起来确实像那么回事,但我不建议大家这么理解,因为他本身是返回一个迭代器对象,其返回值是可以被用于迭代的。我们理解了他被foreach迭代时,其内部是如运作的之后更易于理解yield关键字的本质。
yield 的特性,包含以下几点。
1.yield只能用于函数内部,在非函数内部运用会抛出错误。
2.如果函数包含了yield关键字的,那么函数执行后的返回值永远都是一个Generator对象。
3.如果函数内部同事包含yield和return 该函数的返回值依然是Generator对象,但是在生成Generator对象时,return语句后的代码被忽略。
4.Generator类实现了Iterator接口。
5.可以通过返回的Generator对象内部的方法,获取到函数内部yield后面表达式的值。
6.可以通过Generator的send方法给yield 关键字赋一个值。
7.一旦返回的Generator对象被遍历完成,便不能调用他的rewind方法来重置
8.Generator对象不能被clone关键字克隆