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

PHP fgetcsv() 函數完整指南:CSV 解析、編碼處理與大檔案串流讀取

     編輯:PHP 相關知識

PHP fgetcsv() 函數完整指南:CSV 解析、編碼處理與大檔案串流讀取

fgetcsv() 是 PHP 內建的 CSV 行解析函數,能正確處理欄位包含逗號、引號、換行等複雜情況。本文詳細說明其參數、編碼陷阱、大檔案處理策略與實務最佳實踐。

函數原型與參數

fgetcsv(
    resource $handle,      // fopen() 回傳的檔案指標
    int $length = 0,       // 最大行長度 (0 = 無限制,PHP 5.1+ 建議省略)
    string $separator = ',', // 分隔符 (單一字元)
    string $enclosure = '"', // 包裹字元 (單一字元)
    string $escape = '\\'    // 跳脫字元 (單一字元,PHP 7.4+)
): array|false

基本使用範例

<?php
$file = fopen('data.csv', 'r');
if (!$file) die('無法開啟檔案');

// 可選:跳過標題列
$header = fgetcsv($file);
echo "欄位: " . implode(', ', $header) . "\n";

while (($row = fgetcsv($file)) !== false) {
    // $row 為數值索引陣列 [欄位1, 欄位2, ...]
    echo "ID: {$row[0]}, Name: {$row[1]}, Email: {$row[2]}\n";
}

fclose($file);
?>

CSV 格式邊界情況測試

測試檔案 test.csv 內容:

id,name,description
1,Normal,"Simple value"
2,"With, comma","Contains, comma"
3,"Quotes ""escaped""","Double quotes"
4,"Multi
line","Newline in field"
5,,Empty field
6,"Trailing comma,",

編碼處理:關鍵陷阱與解決方案

⚠️ 核心問題: fgetcsv() 不處理字元編碼,它按位元組切割。若 CSV 為 Big5/GBK 但 PHP 腳本為 UTF-8,中文會變亂碼或切割錯誤。

方案 1:讀取前轉換編碼 (推薦)

<?php
function fopen_utf8($filename, $mode = 'r') {
    $handle = fopen($filename, $mode);
    if (!$handle) return false;
    
    // 偵測 BOM
    $bom = fread($handle, 3);
    if ($bom !== "\xEF\xBB\xBF") {
        rewind($handle); // 無 BOM,回到開頭
    }
    return $handle;
}

$handle = fopen_utf8('big5.csv');
if (!$handle) die('開啟失敗');

// 逐行讀取並轉碼
while (($row = fgetcsv($handle)) !== false) {
    $row = array_map(function($v) {
        return mb_convert_encoding($v, 'UTF-8', 'BIG-5');
    }, $row);
    // 處理 $row...
}
fclose($handle);
?>

方案 2:使用 SplFileObject + 過濾器 (PHP 5.4+)

<?php
$file = new SplFileObject('big5.csv');
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY);
$file->setCsvControl(','); // 分隔符

// 註冊編碼轉換過濾器 (需 php_stream_filter)
stream_filter_register('convert.big5-utf8', 'php_user_filter'); // 簡化示意

foreach ($file as $row) {
    // $row 已是陣列,但編碼仍為原始
    $row = array_map('mb_convert_encoding', $row, array_fill(0, count($row), 'UTF-8'), array_fill(0, count($row), 'BIG-5'));
    print_r($row);
}
?>

方案 3:預先轉檔 (大檔案最佳)

# Linux 指令列預處理
iconv -f BIG5 -t UTF-8 big5.csv > utf8.csv

# PHP 內執行
exec('iconv -f BIG5 -t UTF-8 ' . escapeshellarg($input) . ' > ' . escapeshellarg($output));
// 然後用 UTF-8 模式讀取 utf8.csv

大檔案串流處理策略

<?php
function processLargeCsv($filename, callable $callback, $chunkSize = 1000) {
    $handle = fopen($filename, 'r');
    if (!$handle) throw new Exception("無法開啟: $filename");
    
    $header = fgetcsv($handle);
    $buffer = [];
    $count = 0;
    
    while (($row = fgetcsv($handle)) !== false) {
        $buffer[] = array_combine($header, $row); // 關聯陣列
        $count++;
        
        if (count($buffer) >= $chunkSize) {
            $callback($buffer, $count);
            $buffer = [];
            // 可選:釋放記憶體
            gc_collect_cycles();
        }
    }
    
    // 處理剩餘
    if ($buffer) {
        $callback($buffer, $count);
    }
    
    fclose($handle);
    return $count;
}

// 使用:分批寫入資料庫
processLargeCsv('huge.csv', function($rows, $total) use ($pdo) {
    $sql = "INSERT INTO users (name, email, phone) VALUES (?, ?, ?)";
    $stmt = $pdo->prepare($sql);
    $pdo->beginTransaction();
    foreach ($rows as $row) {
        $stmt->execute([$row['name'], $row['email'], $row['phone']]);
    }
    $pdo->commit();
    echo "已處理 $total 筆\n";
}, 5000);
?>

常見參數調整場景

場景參數設定說明
分號分隔 (歐洲格式)fgetcsv($fp, 0, ';')第二參數 0 表示無長度限制
Tab 分隔 (TSV)fgetcsv($fp, 0, "\t")分隔符可為任何單一字元
單引號包裹fgetcsv($fp, 0, ',', "'")第三參數 enclosure
欄位含逗號、引號、換行預設即支援需正確包裹在 enclosure 內
跳脫字元非反斜線fgetcsv($fp, 0, ',', '"', '|')第五參數 escape (PHP 7.4+)

錯誤處理與資料驗證

<?php
function validateCsvRow(array $row, array $rules) {
    $errors = [];
    foreach ($rules as $field => $rule) {
        $value = $row[$field] ?? null;
        
        // 必填
        if (($rule['required'] ?? false) && ($value === null || $value === '')) {
            $errors[$field] = '必填欄位';
            continue;
        }
        if ($value === null) continue; // 非必填且無值,跳過後續驗證
        
        // 型別/格式
        if (isset($rule['type'])) {
            switch ($rule['type']) {
                case 'int':
                    if (!ctype_digit($value)) $errors[$field] = '需為整數';
                    break;
                case 'email':
                    if (!filter_var($value, FILTER_VALIDATE_EMAIL)) $errors[$field] = 'Email 格式錯誤';
                    break;
                case 'date':
                    $d = DateTime::createFromFormat('Y-m-d', $value);
                    if (!$d || $d->format('Y-m-d') !== $value) $errors[$field] = '日期格式 YYYY-MM-DD';
                    break;
            }
        }
        
        // 長度
        if (isset($rule['max_len']) && mb_strlen($value) > $rule['max_len']) {
            $errors[$field] = "長度超過 {$rule['max_len']}";
        }
    }
    return $errors;
}

// 整合範例
$rules = [
    'id'    => ['required'=>true, 'type'=>'int'],
    'name'  => ['required'=>true, 'max_len'=>50],
    'email' => ['required'=>true, 'type'=>'email'],
];

$handle = fopen('users.csv', 'r');
$header = fgetcsv($handle);
$line = 1;
while (($row = fgetcsv($handle)) !== false) {
    $line++;
    $data = array_combine($header, $row);
    $errors = validateCsvRow($data, $rules);
    if ($errors) {
        error_log("第 $line 行驗證失敗: " . json_encode($errors));
        continue; // 或寫入錯誤檔
    }
    // 正常處理...
}
fclose($handle);
?>

效能比較

方法速度記憶體功能建議場景
fgetcsv() 迴圈快低 (串流)基礎解析大檔案、記憶體受限
SplFileObject::READ_CSV快低迭代器介面、可求長度物件導向風格
str_getcsv() + file()慢高 (全載入)單行解析小檔案、簡單腳本
League\Csv (套件)中中豐富 (轉換、驗證、選單)複雜專案、需維護性

最佳實務總結

  1. 明確指定編碼:不要假設 CSV 編碼,必須轉 UTF-8 再處理
  2. 串流處理大檔:永遠用 fgetcsv() 迴圈,不要 file() 全讀入
  3. 驗證不可少:外部資料視為不信任,逐欄驗證型別、長度、格式
  4. 錯誤隔離:單行錯誤不應中斷整批匯入,記錄錯誤行繼續處理
  5. 資料庫批次寫入:使用 Transaction + Prepared Statement,每 500-5000 筆 commit 一次
  6. 臨時檔清理:轉碼產生的暫存檔處理完畢立即刪除
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved