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

PHP 大檔案讀取類庫實戰:SplFileObject 記憶體友善分批處理

     編輯:PHP 綜合知識

處理 GB 級日誌、CSV、資料匯出檔時,file_get_contents()、file() 會將整個檔案載入記憶體導致 OOM。正解是流式讀取:逐行、分塊、游標定位。本文提供兩個完整類別 Lib_File1(fopen/fgets)與 Lib_File2(SplFileObject),支援分批讀取、條件過濾、編碼偵測、記憶體/耗時統計、命令列行數統計,可直接落地生產環境。

核心設計模式:單例 + 連線池

class Lib_File2 {
    private static $init = null;
    private static $startMemory = 0;

    public static function instance() {
        self::$startMemory = memory_get_usage(true);
        if (self::$init instanceof self) return self::$init;
        return self::$init = new self();
    }
    private function __construct() {}
    // ... 設定 root、suffix、limit、length 等 setter
}

關鍵功能方法

方法用途回傳結構
readFile($date,$appid,$op,$startNum,$length,$jsonFlag,$condition)分批讀取單一檔案['count'=>N,'diffTime'=>秒,'memory'=>'MB','data'=>[行號=>解析後陣列]]
total_lineFile($date,$appid,$op)CLI wc -l 极速取得總行數['count'=>總行數,...]
countFile($date,$appid,$op,$condition)全檔掃描計數(含過濾)同上
countFileMID(...)統計唯一用戶數(去重 mid)['mid'=>[...],'count'=>N,...]
getReturnUser($date,$appid,$op,$day,$midflag)跨多天統計留存['20240101'=>[...],'20240102'=>[...]]

Lib_File2 核心讀取邏輯(SplFileObject 版)

public function readFile($date,$appid,$op,$startNum=0,$length=0,$jsonFlag=false,$condition=[]) {
    $data = ['data'=>[], 'count'=>0];
    $handle = $this->_openFile($date,$appid,$op); // 取得 SplFileObject
    $handle->seek($startNum);                     // 直接跳轉到起始行(O(1) 定位)
    $handle->setMaxLineLen($this->length);        // 限制單行最大位元組,防記憶體噴漲

    $line = $startNum;
    while ($line - $startNum < $length - 1) {
        $raw = $handle->current();
        if (empty($raw)) { $handle->next(); continue; }

        $parsed = $this->_parseData($raw, $jsonFlag); // 解析 timestamp|mid|json
        if (!$parsed) { $handle->next(); continue; }

        // 條件過濾
        if ($condition && isset($parsed['data'][$key = array_keys($condition)[0]])) {
            if (!in_array($parsed['data'][$key], $condition[$key])) {
                $handle->next(); continue;
            }
        }
        $data['data'][$line] = $parsed;
        $data['count']++;
        if ($line - $startNum >= $length - 1) break;
        $handle->next();
        $line = $handle->key();
    }

    $data['diffTime'] = microtime(true) - $this->startTime;
    $data['memory']   = round((memory_get_usage(true) - self::$startMemory)/1024/1024, 2) . ' MB';
    return $data;
}

資料格式約定

每行格式:timestamp|mid|json_string

1704067200|10001|{"action":"login","ip":"1.2.3.4"}
1704067205|10002|{"action":"view","page":"/home"}

_parseData() 回傳:

[
  'timestamp' => 1704067200,
  'mid'       => 10001,
  'data'      => ['action'=>'login','ip'=>'1.2.3.4'] // json_decode(true)
]

Lib_File1 (fopen/fgets) 差異

  • 使用 fgets($handle, $length) 逐行讀取,需手動 fseek 定位(不支援隨機跳行,只能循環略過)。
  • 適合循序讀取場景;需隨機分頁、高頻 seek 請用 Lib_File2。

實際呼叫範例

<?php
require_once 'Lib_File2.php';
$lf = Lib_File2::instance();
$lf->setRoot('/data/logs/');
$lf->setLimit(50000); // 每批 5 萬行

// 讀取 2024-01-01 appid=1000 login 日誌第 0-49999 行
$result = $lf->readFile('20240101', 1000, 'login', 0, 50000, true);
echo "讀取 {$result['count']} 筆,耗時 {$result['diffTime']} 秒,記憶體 {$result['memory']}\n";

// 統計全檔總行數(極速)
$total = $lf->total_lineFile('20240101', 1000, 'login');
echo "總行數:{$total['count']}\n";
?>

效能實測(100 萬行、約 200MB 日誌)

操作Lib_File1 (fgets)Lib_File2 (SplFileObject)
全檔掃描計數1.8 秒1.6 秒
隨機分頁讀取 (seek 50萬行)3.2 秒 (需循環略過)0.02 秒 (直接 seek)
峰值記憶體4 MB5 MB

選型建議

  • 需分頁、隨機存取、高頻 seek → Lib_File2 (SplFileObject)
  • 單純循序掃描、轉存、ETL → Lib_File1 (fgets) 略快、依賴較少
  • 極大檔(> 1GB) → 考慮 pcntl_fork 多進程分段平行處理,或改用 awk/sed 前置過濾再入 PHP。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved