處理 GB 級 CSV 不能一次 file() 讀入記憶體。本文示範用 SplFileObject 實作 Iterator 逐行讀取、自動編碼轉換、欄位對應物件,並提供寫入大檔案的緩衝策略。
<?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);
}
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']);
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';
}
| 問題 | 解決 |
|---|---|
| 欄位含逗號/換行 | fgetcsv/fputcsv 自動處理 enclosure,確保資料正確 |
| BOM 導致第一欄位名亂碼 | 讀取時略過 BOM:fseek($fp, 3) 若偵測到 BOM |
| 大檔案記憶體爆炸 | **必須**用 Iterator/Generator 逐行,禁用 file()/str_getcsv() 全讀 |
| Excel 開啟科學符號 | 匯出時在數值前加 \t 或設定欄位格式為文字 |
| 多語言編碼混雜 | 統一轉 UTF-8 儲存,匯出時視目標系統轉 BIG5/GBK |