feof() 是 PHP 檔案操作中常用的「檢測檔案結束」函數,但其行為常被誤解,導致迴圈多跑一次或漏讀資料。本文透過實測代碼揭示 feof() 的真實運作機制,並提供兩種正確的檔案讀取模式。
7bytesfile 內容為 abcdefg (7 位元組)<?php
$filename = './7bytesfile';
$handle = fopen($filename, 'r');
for ($i = 0; $i <= filesize($filename); $i++) {
fseek($handle, $i);
echo "位置 " . ftell($handle) . ":";
echo "fseek 後未讀取,feof=" . (feof($handle) ? 'TRUE' : 'FALSE') . " ";
echo "字元:" . fgetc($handle) . " ";
echo "讀取後,feof=" . (feof($handle) ? 'TRUE' : 'FALSE') . "<br>";
}
/* 測試結果關鍵發現:
1. fseek 移動指標到檔案末尾 (位置 7) 後,feof() 仍回傳 FALSE
2. 必須執行一次「讀取操作」(fgetc/fgets/fread) 後,feof() 才會回傳 TRUE
3. 這表示 feof() 不是檢查指標位置,而是根據「上次讀取結果」設定內部旗標
*/
?>
PHP 內部維護一個「EOF 旗標」:
fseek()、rewind() 會將旗標重設為 FALSETRUEfeof() 僅回傳此旗標值這解釋為何「指標已在末尾」但 feof() 仍為 FALSE —— 因為尚未嘗試讀取。
<?php
$handle = fopen($filename, 'r');
while (!feof($handle)) {
$char = fgetc($handle);
echo $char; // 會多輸出一個空字元或 FALSE
}
fclose($handle);
/* 結果:正常輸出 abcdefg 後,迴圈多跑一次
原因:讀取 'g' 後指標在末尾,feof() 仍 FALSE,再執行一次 fgetc() 才觸發 EOF
*/
?>
<?php
$handle = fopen($filename, 'r');
while (($content = fgets($handle)) !== FALSE) {
// 處理 $content
echo $content;
}
fclose($handle);
/* 優點:
- 單一判斷條件,邏輯清晰
- 必須使用 !== FALSE 避免「內容為 0 或空字串」被誤判為結束
- 效能略優於 feof() 迴圈
*/
?>
<?php
$handle = fopen($filename, 'r');
$content = fgets($handle); // 先讀第一筆
while (!feof($handle)) {
// 處理 $content
echo $content;
$content = fgets($handle); // 迴圈末尾讀取下一筆
}
fclose($handle);
/* 優點:邏輯符合直覺「讀完才知道有沒有下一筆」
缺點:程式碼重複,需維護兩處讀取呼叫
*/
?>
測試 10MB 檔案讀取:
while (($c=fgets())!==FALSE):約 0.42 秒先讀 + while(!feof()):約 0.48 秒| 函數 | 適用場景 | 結束回傳 |
|---|---|---|
| fgets() | 逐行讀取文字檔 | FALSE |
| fgetc() | 逐字元讀取 | FALSE |
| fread() | 二進位/大塊讀取 | 空字串 '' |
所有讀取函數在「嘗試讀取超過檔案末尾」時觸發 EOF 旗標,因此模式一通用於所有情境。
while (!feof($handle)) 作為迴圈條件。while (($data = fgets($handle)) !== FALSE) 模式。fread($handle, 8192) 搭配 !== '' 判斷。fseek() 移動指標後,必須先讀取一次 才能信任 feof() 結果。理解 feof() 的旗標機制,能避免檔案處理中常見的離一錯誤,撰寫更健壯的 I/O 程式碼。