需求:將 100 萬行資料寫入文字檔。實測 fwrite 僅需約 10 秒,file_put_contents (搭配 FILE_APPEND) 卻耗時 300 秒且漏寫資料。本文詳細記錄測試過程、程式碼與結論。
<?php
function microtimeFloat() {
list($usec, $sec) = explode(' ', microtime());
return (float)$usec + (float)$sec;
}
$userCount = 1000;
$itemCount = 1000;
$file = 'ratings.txt';
if (file_exists($file)) unlink($file);
$timeStart = microtimeFloat();
for ($i = 0; $i < $userCount; $i++) {
$uid = bin2hex(random_bytes(16)); // 32 字元隨機 ID
for ($j = 0; $j < $itemCount; $j++) {
$itemId = mt_rand(1, 300000);
$rating = $j === 0 ? 1 : mt_rand(1, 100) / 100;
$line = sprintf("%s,%d,%.2f\n", $uid, $itemId, $rating);
file_put_contents($file, $line, FILE_APPEND); // 每行開啟、寫入、關閉檔案
}
}
$timeEnd = microtimeFloat();
echo "file_put_contents 耗時: " . ($timeEnd - $timeStart) . " 秒\n";
// 實測輸出: 307 秒,且檔案僅 999,997 行 (漏 3 行),過程中出現 Permission denied 警告
?>
<?php
function microtimeFloat() {
list($usec, $sec) = explode(' ', microtime());
return (float)$usec + (float)$sec;
}
$userCount = 1000;
$itemCount = 1000;
$file = 'ratings_fwrite.txt';
if (file_exists($file)) unlink($file);
$fp = fopen($file, 'ab'); // 以二進位附加模式開啟一次
if (!$fp) die("無法開啟檔案: $file");
$timeStart = microtimeFloat();
for ($i = 0; $i < $itemCount; $i++) { // 為了測試速度調整迴圈順序
$uid = bin2hex(random_bytes(16));
for ($j = 0; $j < $userCount; $j++) {
$itemId = mt_rand(1, 300000);
$rating = $j === 0 ? 1 : mt_rand(1, 100) / 100;
$line = sprintf("%s,%d,%.2f\n", $uid, $itemId, $rating);
fwrite($fp, $line);
}
}
fclose($fp);
$timeEnd = microtimeFloat();
echo "fwrite 耗時: " . ($timeEnd - $timeStart) . " 秒\n";
// 實測輸出: 10.7 秒,檔案完整 1,000,000 行
?>
| 指標 | file_put_contents + FILE_APPEND | fwrite (資源控制代碼) |
|---|---|---|
| 系統呼叫次數 | 1,000,000 次 (每行 open/write/close) | 1 次 open + 1,000,000 次 write + 1 次 close |
| 檔案鎖定/解鎖 | 每行各一次 | 僅開啟/關閉時 |
| 緩衝機制 | 無使用者層級緩衝,依賴 OS | PHP 內部緩衝 (預設 8KB),批量寫入磁碟 |
| 錯誤率 | 高併發下易發生 Permission denied、資料遺失 | 極低,單一控制代碼管理 |
| 實測耗時 | ~307 秒 | ~10 秒 |
| 資料完整性 | 漏寫 | 完整 |
fopen + fwrite + fclose。flock($fp, LOCK_EX) 獨佔鎖定,寫入後 flock($fp, LOCK_UN) 釋放。stream_set_chunk_size() 調整緩衝、或直接用資料庫 LOAD DATA INFILE / COPY 批量導入。file_put_contents 程式碼簡潔,效能差異可忽略,可使用。<?php
$file = new SplFileObject('data.txt', 'wb'); // 寫入模式,自動處理關閉
$file->setFlags(SplFileObject::DROP_NEW_LINE); // 不自動加換行
for ($i = 0; $i < 1000000; $i++) {
$file->fwrite("第 $i 行資料\n");
}
// 物件銷毀時自動 fclose
?>
SplFileObject 內部同樣使用緩衝寫入,效能接近 fwrite,程式碼更現代化且自動資源管理。