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

PHP 固定寬度文字檔轉 CSV 類別:SplFileObject 迭代、欄位選擇、分隔符自訂、高效能轉換

     編輯:PHP 綜合知識

固定寬度 (Fixed-width) 文字檔常見於銀行匯款、主機下載、政府開放資料。本文提供 Fixed2Csv 類別繼承 SplFileObject,利用迭代器逐行轉換,支援欄位位置/寬度陣列、分隔符/換行符自訂、欄位選擇性輸出、記憶體友善,適合大檔案 ETL、資料庫匯入前處理。

核心類別 Fixed2Csv

<?php
/**
 * 固定寬度檔案轉 CSV 轉換器
 * 繼承 SplFileObject 利用內建迭代器,記憶體效率高
 */
class Fixed2Csv extends SplFileObject {
    private $fields = [];      // [開始位置 => 寬度]
    private $separator = ',';  // CSV 分隔符
    private $eol = "\n";       // 換行符

    public function __construct($filename) {
        parent::__construct($filename, 'r');
    }

    // 動態屬性設定
    public function __set($name, $value) {
        switch ($name) {
            case 'fields':
            case 'separator':
            case 'eol':
                $this->$name = $value;
                break;
            default:
                throw new Exception("無法設定屬性: $name");
        }
    }

    // 動態屬性讀取 (提供預設值)
    public function __get($name) {
        switch ($name) {
            case 'fields': return [];
            case 'separator': return ',';
            case 'eol': return "\n";
            default: throw new Exception("無法讀取屬性: $name");
        }
    }

    /**
     * 覆寫 current():將固定寬度行轉為 CSV 格式
     * @return string|false CSV 行或 false (檔案結束)
     */
    public function current() {
        $line = parent::current();
        if ($line === false) return false;

        $csv = '';
        $fieldIter = new CachingIterator(new ArrayIterator($this->fields));
        foreach ($fieldIter as $width) {
            $start = $fieldIter->key();
            $value = trim(substr($line, $start, $width));
            $csv .= $value;
            $csv .= $fieldIter->hasNext() ? $this->separator : $this->eol;
        }
        return $csv;
    }
}
?>

使用範例

假設 data.txt 為固定寬度格式:

ID(10)NAME(15)AMOUNT(12)DATE(8)
0000000001John Doe      00001234.5620240115
0000000002Jane Smith    00000987.0020240116
<?php
require 'Fixed2Csv.class.php';

try {
    // 1. 完整轉換所有欄位
    $file = new Fixed2Csv('data.txt');
    $file->fields = [
        0  => 10,   // ID: 位置 0, 寬度 10
        10 => 15,   // NAME: 位置 10, 寬度 15
        25 => 12,   // AMOUNT: 位置 25, 寬度 12
        37 => 8,    // DATE: 位置 37, 寬度 8
    ];
    $file->separator = ',';
    $file->eol = "\n";

    echo "完整轉換:\n";
    foreach ($file as $line) {
        echo $line;
    }

    // 2. 僅輸出 ID 與 AMOUNT (欄位選擇)
    echo "\n--- 僅 ID 與 AMOUNT ---\n";
    $file2 = new Fixed2Csv('data.txt');
    $file2->fields = [
        0  => 10,   // ID
        25 => 12,   // AMOUNT
    ];
    $file2->separator = '|';
    foreach ($file2 as $line) {
        echo $line;
    }

    // 3. 直接寫入檔案 (大檔案建議)
    $output = fopen('output.csv', 'w');
    foreach ($file as $line) {
        fwrite($output, $line);
    }
    fclose($output);
    echo "\n已輸出至 output.csv\n";

} catch (Exception $e) {
    echo "錯誤: " . $e->getMessage();
}
?>

輸出結果

完整轉換:
0000000001,John Doe,00001234.56,20240115
0000000002,Jane Smith,00000987.00,20240116

--- 僅 ID 與 AMOUNT ---
0000000001|00001234.56
0000000002|00000987.00

進階擴充:支援標題列、型別轉換、驗證

<?php
class AdvancedFixed2Csv extends Fixed2Csv {
    private $hasHeader = false;
    private $headerMap = [];  // 欄位名 => [start, width]
    private $typeMap = [];    // 欄位名 => callable 轉換函式

    public function setHeaderMap(array $map) {
        $this->headerMap = $map;
        // 將關聯陣列轉為位置索引陣列供父類別使用
        $this->fields = [];
        foreach ($map as $name => $def) {
            $this->fields[$def['start']] = $def['width'];
        }
        ksort($this->fields);  // 確保位置順序
    }

    public function setTypeMap(array $map) {
        $this->typeMap = $map;
    }

    public function current() {
        $csv = parent::current();
        if ($csv === false) return false;

        // 若需型別轉換,這裡可進一步處理 (需解析 CSV 再組裝)
        // 為效率考量,建議在消費端處理型別轉換
        return $csv;
    }

    // 靜態工廠方法:從標題列自動偵測欄位寬度
    public static function fromHeaderLine($filename, $headerLineNum = 0) {
        // 實作略:讀取標題列,分析欄位邊界 (連續空白/特定分隔符)
    }
}
?>

效能優化重點

資料庫匯入直連方案

<?php
// 生成 CSV 暫存檔
$converter = new Fixed2Csv('big_data.txt');
$converter->fields = [0=>10, 10=>20, 30=>15, 45=>8];
$converter->separator = ',';
$converter->eol = "\n";

$tmp = tempnam(sys_get_temp_dir(), 'csv_');
$fp = fopen($tmp, 'w');
foreach ($converter as $line) fwrite($fp, $line);
fclose($fp);

// MySQL LOAD DATA INFILE 秒級匯入百萬筆
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$pdo->exec("
    LOAD DATA LOCAL INFILE '$tmp'
    INTO TABLE transactions
    FIELDS TERMINATED BY ',' ENCLOSED BY '\"'
    LINES TERMINATED BY '\n'
    IGNORE 1 LINES
    (id, name, amount, txn_date)
");
unlink($tmp);
echo "匯入完成";
?>

常見格式對照表

技術效益
繼承 SplFileObject內建檔案指標管理、迭代器介面、逐行讀取不佔記憶體
CachingIterator + ArrayIterator單次遍歷即知是否為最後欄位,決定分隔符/換行符
trim() 移除填充空白固定寬度常以空白填充,trim 乾淨去除
可選欄位輸出僅處理需要的欄位,減少 CPU/IO
分隔符/換行符可配置支援 CSV/TSV/自訂格式,直接餵資料庫 LOAD DATA INFILE

將 Fixed2Csv 封裝為 Composer 套件,整合至 ETL 流程 (如 Laravel Jobs、Symfony Messenger、獨立 CLI),即可構建穩健的固定寬度檔案處理管線。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
來源格式特徵轉換關鍵
COBOL 複製本PIC 9(10) / PIC X(20) 定義解析 Copybook 產生 fields 陣列
銀行 ACH/匯款固定 94/120/... 字元/行依規格書定義位置寬度
主機報表列印對齊欄位、空白填充trim() 處理、注意數字前導零
政府開放資料常提供 CSV 已轉換優先用現成 CSV,避免重複造輪