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

PHP CSV 處理最佳實踐:SplFileObject 迭代器、記憶體優化與編碼轉換

     編輯:PHP 相關知識

PHP 讀寫大型 CSV 檔案:SplFileObject 實作 Iterator、分塊處理與編碼自動偵測

處理 GB 級 CSV 不能一次 file() 讀入記憶體。本文示範用 SplFileObject 實作 Iterator 逐行讀取、自動編碼轉換、欄位對應物件,並提供寫入大檔案的緩衝策略。

1. 只讀迭代器:CSVIterator

<?php
class CSVIterator implements Iterator {
    private $file;
    private $fp;
    private $headers = [];
    private $current = [];
    private $key = 0;
    private $delimiter;
    private $enclosure;
    private $encoding;

    public function __construct(string $file, string $delimiter = ',', string $enclosure = '"', string $encoding = 'UTF-8') {
        $this->file = $file;
        $this->delimiter = $delimiter;
        $this->enclosure = $enclosure;
        $this->encoding = $encoding;
    }

    public function rewind(): void {
        if ($this->fp) fclose($this->fp);
        $this->fp = fopen($this->file, 'rb');
        if (!$this->fp) throw new RuntimeException("Cannot open {$this->file}");
        // 讀取標題行
        $this->headers = $this->readRow() ?: [];
        $this->key = 0;
    }

    public function current(): array {
        return $this->current;
    }

    public function key(): int {
        return $this->key;
    }

    public function next(): void {
        $this->current = $this->readRow();
        $this->key++;
    }

    public function valid(): bool {
        return $this->current !== false;
    }

    private function readRow(): ?array {
        if (!$this->fp) return null;
        $row = fgetcsv($this->fp, 0, $this->delimiter, $this->enclosure);
        if ($row === false) return null;
        // 編碼轉換
        if ($this->encoding !== 'UTF-8') {
            $row = array_map(fn($v) => mb_convert_encoding($v, 'UTF-8', $this->encoding), $row);
        }
        // 對應標題
        if ($this->headers) {
            return array_combine($this->headers, $row);
        }
        return $row;
    }

    public function __destruct() {
        if ($this->fp) fclose($this->fp);
    }
}

// 使用:逐行處理,記憶體恆定
foreach (new CSVIterator('big_data.csv', ',', '"', 'BIG5') as $row) {
    // $row 為關聯陣列 ['col1' => 'val1', ...]
    process_row($row);
}

2. 寫入大檔案:緩衝區策略

function write_csv_chunked(string $file, iterable $rows, array $headers, int $bufferSize = 1000): void {
    $fp = fopen($file, 'wb');
    // 寫入 BOM (Excel 相容)
    fwrite($fp, "\xEF\xBB\xBF");
    // 寫入標題
    fputcsv($fp, $headers);

    $buffer = [];
    foreach ($rows as $row) {
        $buffer[] = $row;
        if (count($buffer) >= $bufferSize) {
            foreach ($buffer as $r) fputcsv($fp, $r);
            $buffer = [];
        }
    }
    // 餘數
    foreach ($buffer as $r) fputcsv($fp, $r);
    fclose($fp);
}

// 使用:產生器提供資料列
function data_generator(): Generator {
    for ($i = 1; $i <= 100000; $i++) {
        yield [$i, "Name $i", "email$i@example.com", date('Y-m-d')];
    }
}
write_csv_chunked('export.csv', data_generator(), ['id', 'name', 'email', 'date']);

3. 編碼自動偵測

function detect_csv_encoding(string $file): string {
    $content = file_get_contents($file, false, null, 0, 4096); // 讀前 4KB
    $enc = mb_detect_encoding($content, ['UTF-8', 'BIG5', 'GBK', 'CP950', 'ISO-8859-1'], true);
    return $enc ?: 'UTF-8';
}

4. 常見問題與解決

問題解決
欄位含逗號/換行fgetcsv/fputcsv 自動處理 enclosure,確保資料正確
BOM 導致第一欄位名亂碼讀取時略過 BOM:fseek($fp, 3) 若偵測到 BOM
大檔案記憶體爆炸**必須**用 Iterator/Generator 逐行,禁用 file()/str_getcsv() 全讀
Excel 開啟科學符號匯出時在數值前加 \t 或設定欄位格式為文字
多語言編碼混雜統一轉 UTF-8 儲存,匯出時視目標系統轉 BIG5/GBK

5. 推薦成熟套件

  • thephpleague/csv — 功能最全、支援 RFC 4180、流式處理、轉換器
  • goodby/csv — 記憶體效率極高、支援對應物件
  • box/spout — 同時支援 CSV/XLSX/ODS、串流讀寫
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved