首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 讀取 CSV 檔案完整指南:fgetcsv、SplFileObject、大檔案串流與編碼陷阱

     編輯:PHP 相關知識

前言:CSV 看似簡單,實則暗藏坑點

RFC 4180 定義 CSV 格式,但實際檔案常見:編碼不一 (UTF-8/BIG5/GBK)、分隔符非逗號、欄位含換行/逗號/引號、BOM 頭、標題列缺失、行數百萬。本文提供生產級讀取方案。

核心工具對比

工具優點缺點適用場景
SplFileObject物件導向、可迭代、內建 CSV 模式、記憶體友善PHP 5.1+、編碼轉換需自行處理✅ 首選 (中小檔)
fgetcsv()程序式、精細控制、相容舊代碼需手動 fopen/fclose、易忘記釋放資源舊專案維護
League\Csv功能最全 (轉換、過濾、驗證、Chunk)、支援 PHP 8.1+額外依賴、學習曲線✅ 複雜需求首選
fgets + str_getcsv自定義分隔符邏輯效能差、易錯極特殊格式

方案一:SplFileObject (推薦標準用法)

<?php
function readCsv(string $path, string $encoding = 'UTF-8', string $delimiter = ','): \Generator {
    $file = new SplFileObject($path, 'r');
    $file->setFlags(
        SplFileObject::READ_CSV |
        SplFileObject::READ_AHEAD | // 預讀下一行
        SplFileObject::SKIP_EMPTY | // 跳過空行
        SplFileObject::DROP_NEW_LINE
    );
    $file->setCsvControl($delimiter, '"', '\\'); // 分隔符、引號、跳脫

    $header = null;
    foreach ($file as $rowNum => $row) {
        // 編碼轉換 (若檔案非 UTF-8)
        if ($encoding !== 'UTF-8') {
            $row = array_map(fn($v) => mb_convert_encoding($v, 'UTF-8', $encoding), $row);
        }

        if ($header === null) {
            $header = $row; // 第一行當標題
            continue;
        }

        // 組合關聯陣列
        yield array_combine($header, $row);
    }
}

// 使用:逐行處理,記憶體恆定
foreach (readCsv('data.csv', 'BIG5') as $record) {
    // $record = ['id'=>'1', 'name'=>'張三', 'email'=>'...']
    processRecord($record);
}

方案二:League\Csv (進階功能)

composer require league/csv

use League\Csv\Reader;
use League\Csv\Statement;

$reader = Reader::createFromPath('big_data.csv', 'r');
$reader->setHeaderOffset(0); // 第一行為標題
$reader->setDelimiter(';');
$reader->setEncoding('ISO-8859-1'); // 內部自動轉 UTF-8

// Statement: 類 SQL 查詢 (篩選、切片、排序)
$stmt = (new Statement())
    ->where(fn($row) => $row['status'] === 'active')
    ->offset(1000)
    ->limit(500);

$records = $stmt->process($reader);
foreach ($records as $record) {
    // $record 為關聯陣列
    exportToApi($record);
}

編碼偵測與轉換最佳實務

  1. 優先檢查 BOM:fopen 讀前 3 bytes,EF BB BF = UTF-8 BOM。
  2. 使用 mb_detect_encoding 輔助:mb_detect_encoding($sample, ['UTF-8', 'BIG5', 'GBK', 'ISO-8859-1'], true)。
  3. 強制指定:由上游系統約定編碼,避免猜測。
  4. 轉換失敗策略:mb_convert_encoding($str, 'UTF-8', $from, 'UTF-8//IGNORE') 或拋出異常記錄錯誤行。

大檔案 (>1GB) 串流處理架構

<?php
class CsvStreamProcessor {
    public function __construct(
        private string $path,
        private int $batchSize = 1000,
        private string $encoding = 'UTF-8'
    ) {}

    public function process(callable $handler): void {
        $reader = Reader::createFromPath($this->path, 'r');
        $reader->setHeaderOffset(0);
        $reader->setEncoding($this->encoding);

        $batch = [];
        foreach ($reader as $record) {
            $batch[] = $record;
            if (count($batch) >= $this->batchSize) {
                $handler($batch);
                $batch = [];
                // 可選:釋放記憶體、GC
                gc_collect_cycles();
            }
        }
        if ($batch) $handler($batch); // 餘數
    }
}

// 使用:批次寫入資料庫、推送隊列
$processor = new CsvStreamProcessor('huge.csv', batchSize: 500);
$processor->process(function(array $batch) {
    DB::table('users')->upsert($batch, 'email', ['name', 'phone']);
    // 或推送 RabbitMQ
    foreach ($batch as $record) $queue->publish($record);
});

常見錯誤對照表

錯誤現象原因解決
欄位數不一致警告某行欄位含未閉合引號、換行檢查來源匯出邏輯、或用 League\Csv 容錯模式
中文亂碼編碼未正確轉換確認來源編碼、統一轉 UTF-8、處理 BOM
記憶體耗盡一次性 file() 或 array_map 載入全檔改用 Generator/Iterator 串流處理
科學記號 (1.2E+10)Excel 匯出長數字自動科學記號匯出前設定欄位格式為文字、或讀取後 sprintf('%.0f', $val)
前導零消失 (00123 → 123)PHP 自動轉型為整數讀取時保持字串、array_combine 不轉型、資料庫存 CHAR/VARCHAR

完整 Library 整合 (Laravel CSV Importer、Symfony CSV Bundle) 見 GitHub。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved