php 处理 10W 及以上csv 文件,使用生成器 yield
前几天有个任务是要解密excel中某个字段,本来是一个非常简单的事情,但由于Excel近10M、有几万数据,使用phpexcel一直load不了,内存和运行时间都已经设为最大,仍然处理不完,尤其是内存,一直会爆掉,而且进程所占CPU爆满,最后各种问度娘,最后想到了前段时间看的生成器 yield, 刚好是一个测试的机会
class Qushu { public function getDg(){ set_time_limit(0); $file = request()->get('file'); $path = 'D:/path/'.$file.'.csv'; $key = '********'; $header = ['订单','姓名','电话','地址','证件号','机构','社会代码','税务','测试','测试电话','得知','物品名称']; $output = $this->csvSet("导出表名",$header); $data = $this->getCsv($path); $i = 0; foreach ($data as $k=>$v) { if (!$v) break; $idcard = iconv('GBK',"UTF-8//TRANSLIT//IGNORE",$v[4]); // 根据条件判断是否要处理字段 if (!empty($idcard) && strlen($idcard)>18){ // 处理字段的code.... $v[4] = ''; } //输出csv内容 fputcsv($output, array_values($v)); $i++; } //关闭文件句柄 fclose($output) or die("can't close php://output"); exit; } /** *获取csv内容 使用 yield */ public function getCsv($fname) { $handle = fopen("$fname", 'rb'); while (feof($handle)===false) { # code... yield fgetcsv($handle); } fclose($handle); } /**设置csv*/ public function csvSet($name,$head) { try { //为fputcsv()函数打开文件句柄 $output = fopen('php://output', 'w') or die("can't open php://output"); //告诉浏览器这个是一个csv文件 header("Content-Type: application/csv"); header("Content-Disposition: attachment; filename=$name.csv"); header('Cache-Control:must-revalidate,post-check=0,pre-check=0'); header('Expires:0'); header('Pragma:public'); // 文件名转码 $name = iconv('utf-8', 'gbk', $name); //输出表头 foreach ($head as $i => $v) { //CSV的Excel支持GBK编码,一定要转换,否则乱码 $head[$i] = iconv('utf-8', 'gbk', $v); } fputcsv($output, $head); return $output; }catch (Exception $e){ } } }
从上面可以看出,只是通过 yield 标识就处理好了一个生成器,调用了 getCsv 方法获取到一个迭代器,那么通过循环此迭代器,进行逻辑操作即可。
注意:
- 虽然yield节约了运行内存,但是运行时间仍然需要,因而需要设置运行时间
总结:
- 生成器,提供了一种更容易的方法实现迭代,性能开销和复杂性大大降低
- 生成器函数看起来像普通的函数,不同的是普通函数返回一个值,而一个生成器可以
yield
生成许多它所需要的值,并且每一次的生成返回值只是暂停当前的执行状态,当下次调用生成器函数时,PHP会从上次暂停的状态继续执行下去 - 使用生成器处理大文件,由于yield 并不是一次取出全部数据,而是生成一个可以循环的迭代器,内部会为生成的值配对连续的整型索引,就像一个非关联的数组。每次循环,根据游标取指定的一条数据,节约内存资源,有效防止内存溢出