PHP读取逐行JSON格式大文件并导出为CSV的最优方案
大文件JSON行转CSV的低内存实现方案
现有实现的核心问题是将全量文件数据加载到数组中常驻内存,文件体积达到GB级时会出现内存占用过高甚至溢出的问题。最优解决方案是改用流式处理链路,全程不持有全量数据,做到逐行读取、逐行处理、逐行写入CSV,内存占用稳定在常数级,和文件大小完全无关。
具体实现
1. 读取逻辑改用生成器返回
将原本返回全量数组的读取方法改为返回生成器(Generator),遍历生成器时才会逐行加载数据,不会一次性把所有内容存入内存:
public function read(string $file): \Generator { $fileHandle = fopen($file, "r"); if ($fileHandle === false) { throw new \Exception('Could not get file handle for: ' . $file); } try { while (!feof($fileHandle)) { $line = trim(fgets($fileHandle)); // 跳过空行 if (empty($line)) { continue; } $user = json_decode($line); // 跳过JSON解码失败的异常行,也可根据业务需求加日志或抛出异常 if (json_last_error() !== JSON_ERROR_NONE) { continue; } yield $user; } } finally { // 无论流程是否异常,都保证关闭文件句柄,避免资源泄漏 fclose($fileHandle); } }
2. 处理逻辑改为流式写入CSV
不要把所有处理后的数据存入数组再统一导出,而是处理一行就写入一行CSV:
public function processInput(string $inputFile, string $outputFile): void { // 打开CSV写入句柄 $csvHandle = fopen($outputFile, 'w'); if ($csvHandle === false) { throw new \Exception('Could not open CSV file for write: ' . $outputFile); } try { // 写入CSV表头,按需调整字段 fputcsv($csvHandle, ['user_id', 'user_name']); // 遍历生成器逐行处理 foreach ($this->read($inputFile) as $user) { $row = [ $user->user_id, strtoupper($user->user_name) // 其他需要处理的字段可在此处扩展 ]; fputcsv($csvHandle, $row); } } finally { fclose($csvHandle); } }
优化补充
如果觉得逐行写入CSV IO频率太高,可以增加小批量写入逻辑,平衡IO性能和内存占用:
public function processInput(string $inputFile, string $outputFile, int $batchSize = 100): void { $csvHandle = fopen($outputFile, 'w'); if ($csvHandle === false) { throw new \Exception('Could not open CSV file for write: ' . $outputFile); } try { fputcsv($csvHandle, ['user_id', 'user_name']); $batch = []; foreach ($this->read($inputFile) as $user) { $batch[] = [ $user->user_id, strtoupper($user->user_name) ]; // 攒够指定批次大小再统一写入 if (count($batch) >= $batchSize) { foreach ($batch as $row) { fputcsv($csvHandle, $row); } $batch = []; } } // 写入最后不足批次大小的剩余数据 if (!empty($batch)) { foreach ($batch as $row) { fputcsv($csvHandle, $row); } } } finally { fclose($csvHandle); } }
这个方案即使设置批次大小为1000,内存也只会额外存储1000行数据,对整体内存占用几乎没有影响。
内容的提问来源于stack exchange,提问作者user3286692
相关产品推荐
相关产品推荐

