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

PHP 文件讀取全攻略:feof()、fgets()、fread() 正確用法與避坑指南

     編輯:PHP 綜合知識

前言

文件讀取是 PHP 後端開發的基礎技能。本文深入解析 feof() 配合 fgets()、fread() 的正確使用姿勢,揭示常見陷阱(無限循環、大文件內存溢出、編碼問題),並提供生產級最佳實踐代碼。

核心函數速查

函數用途返回值
fopen()打開文件/URL,返回資源句柄resource|false
feof()檢測文件指針是否到達 EOFbool
fgets()按行讀取(含換行符),最多 length-1 字節string|false
fread()按字節讀取指定長度string|false
fclose()關閉文件句柄bool

經典陷阱:feof() 導致的無限循環

<?php
// ❌ 錯誤示範:fopen 失敗返回 false,feof(false) 永遠為 false
$file = @fopen("not_exist.txt", "r");
while (!feof($file)) { // 無限循環!
    echo fgets($file);
}
fclose($file);
?>

正確模式:先判斷句柄有效性

<?php
$file = @fopen("data.txt", "r");
if ($file === false) {
    throw new RuntimeException("無法打開文件:data.txt");
}

while (!feof($file)) {
    $line = fgets($file);
    if ($line === false) break; // 讀取錯誤保護
    echo $line;
}
fclose($file);
?>

三種讀取策略對比

1. 按行讀取(fgets)— 適合文本、日誌、CSV

<?php
function readLines(string $path, callable $callback): void {
    $fp = fopen($path, 'r');
    if (!$fp) throw new RuntimeException("打開失敗:$path");

    while (($line = fgets($fp)) !== false) {
        $callback(rtrim($line, "\r\n"));
    }
    fclose($fp);
}

// 使用
readLines('access.log', function($line) {
    if (strpos($line, 'ERROR') !== false) {
        error_log("[ALERT] $line");
    }
});
?>

2. 按塊讀取(fread)— 適合二進制、大文件流式處理

<?php
function streamCopy(string $src, string $dest, int $chunkSize = 8192): void {
    $in = fopen($src, 'rb');
    $out = fopen($dest, 'wb');
    if (!$in || !$out) throw new RuntimeException("文件打開失敗");

    while (!feof($in)) {
        $data = fread($in, $chunkSize);
        if ($data === false) break;
        fwrite($out, $data);
    }
    fclose($in);
    fclose($out);
}
?>

3. 一次性讀取(file_get_contents)— 僅限小文件

<?php
// 適用於配置文件、模板等小於 memory_limit 的文件
$config = json_decode(file_get_contents('config.json'), true);
?>

進階:處理網絡流與超時

fsockopen() 打開的網絡流同樣適用 feof(),但需注意:

  • 服務端不關閉連接時,feof() 會阻塞至超時(預設 60s)
  • 可用 stream_set_timeout($fp, 10) 設定超時秒數
  • 超時後 feof() 返回 true,但 fgets() 可能返回空字符串
<?php
$fp = fsockopen('www.example.com', 80, $errno, $errstr, 5);
if (!$fp) throw new RuntimeException("連接失敗:$errstr ($errno)");

stream_set_timeout($fp, 10); // 10 秒讀取超時

fwrite($fp, "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: close\r\n\r\n");

$response = '';
while (!feof($fp)) {
    $response .= fread($fp, 8192);
    $info = stream_get_meta_data($fp);
    if ($info['timed_out']) {
        error_log("讀取超時");
        break;
    }
}
fclose($fp);
?>

編碼處理:BOM 與換行符

檢測並移除 UTF-8 BOM

<?php
function removeBom(string $path): void {
    $content = file_get_contents($path);
    if (strlen($content) >= 3 && ord($content[0]) === 0xEF && ord($content[1]) === 0xBB && ord($content[2]) === 0xBF) {
        file_put_contents($path, substr($content, 3));
    }
}
?>

統一換行符為 LF

<?php
$content = str_replace(["\r\n", "\r"], "\n", $content);
?>

生產級工具類:安全、高效、可測試

<?php
class FileReader {
    public static function readLines(string $path, callable $processor, int $bufferSize = 4096): int {
        $fp = @fopen($path, 'r');
        if (!$fp) throw new RuntimeException("無法打開文件:$path");

        $count = 0;
        try {
            while (!feof($fp)) {
                $line = fgets($fp, $bufferSize);
                if ($line === false) break;
                $processor(rtrim($line, "\r\n"));
                $count++;
            }
        } finally {
            fclose($fp);
        }
        return $count;
    }

    public static function readChunks(string $path, callable $processor, int $chunkSize = 8192): int {
        $fp = @fopen($path, 'rb');
        if (!$fp) throw new RuntimeException("無法打開文件:$path");

        $count = 0;
        try {
            while (!feof($fp)) {
                $chunk = fread($fp, $chunkSize);
                if ($chunk === false || $chunk === '') break;
                $processor($chunk);
                $count++;
            }
        } finally {
            fclose($fp);
        }
        return $count;
    }

    public static function tail(string $path, int $lines = 10): array {
        // 高效尾部讀取:反向讀取塊,統計換行符
        $fp = @fopen($path, 'rb');
        if (!$fp) return [];

        $blockSize = 4096;
        $lineCount = 0;
        $content = '';
        $fileSize = fstat($fp)['size'];
        $pos = $fileSize;

        while ($pos > 0 && $lineCount < $lines) {
            $readSize = min($blockSize, $pos);
            $pos -= $readSize;
            fseek($fp, $pos);
            $block = fread($fp, $readSize);
            $content = $block . $content;
            $lineCount += substr_count($block, "\n");
        }
        fclose($fp);

        $allLines = explode("\n", $content);
        return array_slice($allLines, -$lines);
    }
}
?>

常見問題 FAQ

Q: feof() 在最後一行讀取後才返回 true,會不會多循環一次?
A: 是的。fgets() 讀完最後一行後指針在 EOF 前,feof() 仍為 false,再進循環一次讀到 false 才結束。建議用 while (($line = fgets($fp)) !== false) 模式避免此問題。
Q: 讀取超大文件(>10GB)內存爆了怎麼辦?
A: 必須用流式處理(fread/fgets + callback),不可一次性 file_get_contents 或 file()。必要時用 yield 生成器逐行產出。
Q: Windows 下讀取 Linux 文件換行符顯示異常?
A: 用 str_replace(["\r\n", "\r"], "\n", $line) 統一轉為 LF,或開啟 fopen($path, 'rt') 文本模式(PHP 會自動轉換)。

總結

文件讀取看似簡單,實則暗藏陷阱。牢記三原則:先判斷句柄有效性、優先流式處理大文件、善用 finally 確保資源釋放。掌握 feof()、fgets()、fread() 的配合模式,配合生成器、異步隊列,即可輕鬆應對從配置文件到 TB 級日誌的各類讀取場景。

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