萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

PHP 檔案寫入操作完整指南:fopen/fwrite/fclose、鎖定機制與最佳實踐

檔案寫入三步驟核心流程

  1. 打開檔案:`fopen($filename, $mode)` → 返回檔案指標 resource
  2. 寫入資料:`fwrite($handle, $data, $length)` → 返回寫入位元組數
  3. 關閉檔案:`fclose($handle)` → 釋放資源,強制刷新緩衝區

fopen() 模式完整對照表

模式說明指標位置建立檔案截斷檔案讀/寫
r唯讀開頭否否讀
r+讀寫開頭否否讀/寫
w唯寫開頭是是(清空)寫
w+讀寫開頭是是(清空)讀/寫
a唯寫(附加)結尾是否寫
a+讀寫(附加)結尾是否讀/寫
x唯寫(獨佔建立)開頭僅不存在時否寫
x+讀寫(獨佔建立)開頭僅不存在時否讀/寫
c唯寫(不截斷)開頭是否寫
c+讀寫(不截斷)開頭是否讀/寫

基礎寫入範例

<?php
$file = 'data/log.txt';
$data = "Log entry at " . date('Y-m-d H:i:s') . "\n";

// 追加模式(最常用於日誌)
$fp = fopen($file, 'a');
if ($fp === false) {
    die("無法開啟檔案: $file");
}

$bytes = fwrite($fp, $data);
if ($bytes === false) {
    die("寫入失敗");
}

fclose($fp);
echo "寫入 $bytes 位元組";

檔案鎖定:防止並發寫入資料損毀

<?php
$fp = fopen('counter.txt', 'c+');  // 讀寫、不截斷、建立

if (flock($fp, LOCK_EX)) {         // 獨佔鎖(寫入用)
    $count = (int)fread($fp, filesize('counter.txt'));
    $count++;
    
    ftruncate($fp, 0);             // 清空檔案內容
    rewind($fp);                   // 指標回開頭
    fwrite($fp, (string)$count);
    
    flock($fp, LOCK_UN);           // 釋放鎖
} else {
    die("無法取得鎖定,檔案可能被其他程序佔用");
}
fclose($fp);

鎖定類型常數

  • LOCK_SH:共享鎖(讀取用,多人可同時持有)
  • LOCK_EX:獨佔鎖(寫入用,同時間僅一程序持有)
  • LOCK_UN:釋放鎖
  • LOCK_NB:非阻塞模式(配合 LOCK_EX\|LOCK_NB 避免等待)

高階封裝:安全寫入函數

<?php
/**
 * 安全寫入檔案(原子性、鎖定、錯誤處理)
 * @param string $filepath
 * @param string $data
 * @param string $mode 'w' 覆寫 | 'a' 附加
 * @param int $flags LOCK_EX 預設
 * @return bool|int 寫入位元組數或 false
 */
function safe_file_write($filepath, $data, $mode = 'w', $flags = LOCK_EX) {
    // 確保目錄存在
    $dir = dirname($filepath);
    if (!is_dir($dir)) {
        mkdir($dir, 0755, true);
    }
    
    $fp = @fopen($filepath, $mode === 'a' ? 'a' : 'c');
    if (!$fp) return false;
    
    // 非阻塞嘗試鎖定,避免永久等待
    $locked = flock($fp, $flags | LOCK_NB);
    $attempts = 0;
    while (!$locked && $attempts < 10) {
        usleep(10000);  // 10ms
        $locked = flock($fp, $flags | LOCK_NB);
        $attempts++;
    }
    if (!$locked) {
        fclose($fp);
        return false;
    }
    
    // 覆寫模式需截斷
    if ($mode === 'w') {
        ftruncate($fp, 0);
        rewind($fp);
    }
    
    $written = fwrite($fp, $data);
    flock($fp, LOCK_UN);
    fclose($fp);
    
    return $written;
}

// 使用
safe_file_write('data/config.json', json_encode($config, JSON_UNESCAPED_UNICODE));
safe_file_write('logs/app.log', "[$date] $message\n", 'a');

現代替代方案:file_put_contents()

<?php
// 簡單寫入(內部自動 fopen/fwrite/fclose)
file_put_contents('file.txt', $data);                    // 覆寫
file_put_contents('file.txt', $data, FILE_APPEND);       // 附加
file_put_contents('file.txt', $data, FILE_APPEND | LOCK_EX); // 附加+鎖定

// 返回寫入位元組數,失敗返回 false
  • 優點:程式碼簡潔、內部處理資源釋放
  • 缺點:無法精細控制鎖定行為、大檔案會佔用記憶體
  • 大檔案/高併發仍建議用 `fopen`/`fwrite`/`flock` 手動控制

常見錯誤避坑

  • ❌ 忘記 `fclose()` → 檔案鎖定不釋放、資源洩漏、資料未刷新到磁碟
  • ❌ `w` 模式直接寫入重要檔案 → 若寫入中斷檔案損毀 → ✅ 寫暫存檔再 `rename()` 原子替換
  • ❌ 並發寫入無鎖定 → 資料交錯/遺失 → ✅ 必須 `flock()`
  • ❌ Windows 下 `flock()` 不支援網路磁碟 → 需替代方案(資料庫、Redis)

原子性寫入模式(生產環境推薦)

function atomic_write($filepath, $data) {
    $temp = $filepath . '.tmp.' . uniqid();
    $written = @file_put_contents($temp, $data, LOCK_EX);
    if ($written === false) {
        @unlink($temp);
        return false;
    }
    // rename 在同一檔案系統上是原子操作
    if (!@rename($temp, $filepath)) {
        @unlink($temp);
        return false;
    }
    return true;
}
copyright © 萬盛學電腦網 all rights reserved