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

PHP 讀取超大檔案實戰:fseek + fread 分塊讀取避免記憶體爆滿

     編輯:PHP 相關知識

痛點:大檔案讀取導致記憶體耗盡

CSDN 274 MB SQL 洩露檔案、百萬行日誌、GB 級匯出檔 —— 直接用 file_get_contents()、file()、fopen() + fread() 全讀入記憶體,輕易觸發 memory_limit 或 OOM Killer。正確做法是流式分塊讀取,只在記憶體保留當前處理的片段。

核心方案:fseek + fread 精準定位讀取

<?php
/**
 * 讀取大檔案指定行數
 * @param string $filename 檔案路徑
 * @param int    $count    讀取行數
 * @param string $tag      行分隔符(Windows: \r\n, Linux: \n, Mac: \r)
 * @return string HTML 格式內容(含 <br />)
 */
function readBigFile(string $filename, int $count = 20, string $tag = "\n"): string {
    $content   = '';
    $step      = 1;           // 每次讀 1 位元組(可調大如 4096 提升效能)
    $tagLen    = strlen($tag);
    $start     = 0;
    $lineCount = 0;

    $handle = fopen($filename, 'rb'); // 二進位模式避免轉換
    if (!$handle) {
        throw new RuntimeException("無法開啟檔案: $filename");
    }

    while ($lineCount < $count && !feof($handle)) {
        fseek($handle, $start, SEEK_SET);
        $chunk = fread($handle, $step);
        if ($chunk === false || $chunk === '') break;

        $content .= $chunk;
        $start += $step;

        // 檢查尾部是否為分隔符
        if (substr($content, -$tagLen) === $tag) {
            $lineCount++;
            $content .= '<br />';
        }
    }

    fclose($handle);
    return $content;
}

// 使用範例
$filename = 'csdn.sql';
$tag      = "\n"; // Linux 換行
$count    = 100;
echo readBigFile($filename, $count, $tag);

關鍵細節解析

細節說明
二進位模式 rb避免 Windows 自動將 \r\n 轉 \n,導致位移計算錯誤
分隔符 $tag跨平台必須正確指定:Windows \r\n、Linux/Unix \n、Mac \r
$step 調整預設 1 位元組教學用;生產環境建議 4096/8192,配合緩衝區搜尋分隔符
記憶體佔用僅佔用 $content 累積的當前區塊 + 緩衝區,不隨檔案大小增長
效能頻繁 fseek + 小 fread 較慢;大檔案建議改用 fread(8192) 讀大塊,再在記憶體中 strpos 切行

進階:生產級分塊讀取器(支援斷點續讀)

class BigFileReader {
    private $handle;
    private $filename;
    private $bufferSize = 8192;
    private $position = 0; // 記錄上次讀取位置

    public function __construct(string $filename) {
        $this->filename = $filename;
        $this->handle = fopen($filename, 'rb');
        if (!$this->handle) throw new RuntimeException("Open failed: $filename");
    }

    // 讀取下一塊(預設 8KB),回傳字串或 false(EOF)
    public function readChunk(): string|false {
        if (feof($this->handle)) return false;
        $chunk = fread($this->handle, $this->bufferSize);
        $this->position = ftell($this->handle);
        return $chunk;
    }

    // 依行讀取(生成器,記憶體極低)
    public function readLines(): Generator {
        $buffer = '';
        while (!feof($this->handle)) {
            $buffer .= fread($this->handle, $this->bufferSize);
            while (($pos = strpos($buffer, "\n")) !== false) {
                yield substr($buffer, 0, $pos + 1);
                $buffer = substr($buffer, $pos + 1);
            }
        }
        if ($buffer !== '') yield $buffer; // 最後一行無換行
    }

    // 儲存/恢復位置(斷點續讀)
    public function savePosition(): int { return $this->position; }
    public function seekPosition(int $pos): void {
        fseek($this->handle, $pos, SEEK_SET);
        $this->position = $pos;
    }

    public function close(): void { fclose($this->handle); }
}

// 使用範例
$reader = new BigFileReader('huge.log');
foreach ($reader->readLines() as $i => $line) {
    if ($i >= 1000) break; // 只處理前 1000 行
    processLine($line);
}
$reader->close();

避坑指南

  1. 編碼一致性:確認檔案編碼(UTF-8/GBK/BIG5),PHP 讀取後若需處理中文,先 mb_convert_encoding() 統一轉 UTF-8。
  2. 執行時間限制:長時間讀取需 set_time_limit(0) 或分批次處理(配合 Queue/Worker)。
  3. 並發安全:多程序同時讀寫同檔案,需 flock($handle, LOCK_SH) 共享鎖。
  4. 錯誤處理:fopen、fread、fseek 皆需檢查回傳值,拋出異常或記錄日誌。

替代方案比較

方案記憶體速度適用場景
file_get_contents()高(全檔)快< 50MB 文字檔
file() 陣列極高中小檔、需隨機存取行
fread 固定緩衝區低(緩衝區)快大檔案順序處理、串流輸出
fseek + fread 小步長極低慢隨機存取特定位移、教學示範
Generator 分行讀取極低中逐行邏輯處理、記憶體敏感環境
SplFileObject低中物件導向、內建 CSV/行迭代

總結

讀取超大檔案的核心原則是「串流處理、不全量載入」。根據業務需求選擇:

  • 需隨機存取特定位移 → fseek + fread
  • 需逐行邏輯處理 → Generator + 固定緩衝區(推薦)
  • 需物件導向介面 → SplFileObject

搭配位置持久化(ftell/fseek),即可實現斷點續讀、分散式處理,徹底解決記憶體爆滿與執行逾時問題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved