You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP分割大CSV为19个小文件速度过慢,求优化方案

大型CSV按SiteID拆分的PHP脚本优化方案

我编写了一段PHP脚本,用于将大型CSV文件按SiteID分割为19个小型CSV文件,但脚本运行耗时极长,甚至超出了PHP的最大执行时间。为节省内存我采用逐行处理的方式,原本考虑改用fgetcsv方法,但讲师告知该方法速度更慢。

初始脚本

<?php

@date_default_timezone_set("GMT");

ini_set('memory_limit', '512M');
ini_set('max_execution_time', '300');
ini_set('auto_detect_line_endings', TRUE);
ini_set('display_errors', 1);

$inputFile = '/Users/declanryan/Desktop/UNI3.0/year3/WEB/workshop/assgienment/air-quality-data-2003-2022.csv';
$outputDirectory = 'output';

// 创建输出目录(如果不存在)
if (!file_exists($outputDirectory)) {
    mkdir($outputDirectory);
}

$handle = fopen($inputFile, 'r');

if ($handle) {
    $headerRow = null;
    $siteIDs = array();

    while (($data = fgets($handle)) !== false) {
        $row = str_getcsv($data, ";");

        if ($headerRow === null) {
            $headerRow = $row;
            continue; // 跳过表头行
        }

        $siteID = $row[array_search('SiteID', $headerRow)];
        if (!in_array($siteID, $siteIDs)) {
            $siteIDs[] = $siteID;

            $newCSVFilename = $outputDirectory . '/data-' . $siteID . '.csv';
            $f = fopen($newCSVFilename, 'w');

            fputs($f, implode(';', $headerRow) . PHP_EOL);
        }

        $currentCSVFilename = $outputDirectory . '/data-' . $siteID . '.csv';
        $f = fopen($currentCSVFilename, 'a');

        fputs($f, implode(';', $row) . PHP_EOL);

        fclose($f);
    }

    fclose($handle);
}

echo "Done.";
echo "<br>";
echo 'Script took ' . round((microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]), 2) . ' seconds to run.';

?>

改进后的脚本

<?php

@date_default_timezone_set("GMT");

ini_set('memory_limit', '512M');
ini_set('max_execution_time', '300');
ini_set('auto_detect_line_endings', TRUE);
ini_set('display_errors', 1);

$inputFile = '/Users/declanryan/Desktop/UNI3.0/year3/WEB/workshop/assgienment/air-quality-data-2003-2022.csv';
$outputDirectory = 'output';

// 创建输出目录(如果不存在)
if (!file_exists($outputDirectory)) {
    mkdir($outputDirectory);
}

$source = fopen($inputFile, 'r');
if (!$source) {
    exit('无法打开输入文件。');
}

$headerRow = fgetcsv($source, 0, ';');
if (!$headerRow) {
    exit('无法读取表头行。');
}

$columnIndexes = array_flip($headerRow);
$siteIDColumn = $columnIndexes['SiteID'];

$handles = [];

while (($row = fgetcsv($source, 0, ';')) !== false) {
    $siteID = $row[$siteIDColumn];
    if (!isset($handles[$siteID])) {
        $newCSVFilename = $outputDirectory . '/data-' . $siteID . '.csv';
        $handles[$siteID] = fopen($newCSVFilename, 'w');
        if (!$handles[$siteID]) {
            exit('无法为SiteID: ' . $siteID . '打开输出文件。');
        }
        fputcsv($handles[$siteID], $headerRow, ';');
    }

    fputcsv($handles[$siteID], $row, ';');
}

foreach ($handles as $handle) {
    fclose($handle);
}

fclose($source);

echo "Done.";
echo "<br>";
echo 'Script took ' . round((microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]), 2) . ' seconds to run.';

?>

脚本分析与优化建议

改进后脚本的优势

改进后的脚本完全解决了初始脚本的核心性能问题:

  1. 避免频繁的文件打开/关闭:初始脚本每处理一行都要打开、关闭对应SiteID的CSV文件,文件IO的打开/关闭操作是极其耗时的。改进脚本通过维护$handles数组,每个SiteID的文件仅打开一次,直到所有行处理完成后再统一关闭,这会大幅降低IO开销,是性能提升的关键。
  2. 减少重复索引查找:初始脚本每次循环都调用array_search('SiteID', $headerRow)获取列索引,改进脚本通过array_flip仅做一次索引映射,后续直接通过$siteIDColumn获取索引,减少了重复计算。
  3. 更可靠的CSV处理:fgetcsv和fputcsv是PHP专门为CSV设计的函数,能正确处理带引号的特殊字段(比如字段内容包含分号或换行的情况),而初始脚本的str_getcsv + implode组合在处理这类特殊场景时容易出错。讲师关于fgetcsv更慢的说法并不准确——在减少了文件IO开销的前提下,改进脚本的整体性能会远优于初始版本。

更优的文件拆分方案

如果改进后的脚本仍存在超时或性能问题,可以尝试以下方案:

  1. 切换到命令行环境运行:Web环境通常有服务器层面的超时限制,而命令行环境可以通过set_time_limit(0)取消执行时间限制,更适合处理大文件。
  2. 使用原生命令行工具:Linux的awk或sed工具处理文本文件的速度远快于PHP,比如用awk实现拆分:
    # 假设SiteID是第3列,实际使用时替换为对应列数
    awk -F';' 'NR==1{header=$0; next} {file="output/data-"$3".csv"; if(!seen[$3]++) print header > file; print >> file}' air-quality-data-2003-2022.csv
    
  3. 批量写入优化:可以积累100-1000行数据后再一次性写入文件,减少fputcsv的调用次数,但这个优化的提升幅度远低于减少文件打开/关闭的操作。

内容的提问来源于stack exchange,提问作者grimx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 14:37:05