IO 流(Input/Output Stream)是程式與外部設備(文件、網絡、標準輸入輸出)進行數據交換的抽象通道。在 PHP 中,文件操作本質上就是通過流來讀寫數據。
| 維度 | 類型 | 說明 |
|---|---|---|
| 按數據單位 | 位元組流 (Binary Stream) | 按字節讀寫,適用於圖片、視頻、二進制文件 |
| 字符流 (Text Stream) | 按字符讀寫,自動處理編碼轉換,適用於文本文件 | |
| 按流向 | 輸入流 | 從源頭讀取數據到程式(如 fopen($file, 'r')) |
| 輸出流 | 從程式寫入數據到目標(如 fopen($file, 'w')) |
fopen()$handle = fopen(string $filename, string $mode, bool $use_include_path = false, resource $context = null): resource|false
常用模式:
| 模式 | 說明 | 指針位置 | 截斷文件 | 創建文件 |
|---|---|---|---|---|
r | 唯讀 | 開頭 | 否 | 否 |
w | 唯寫 | 開頭 | 是 | 是 |
a | 追加寫入 | 結尾 | 否 | 是 |
r+ | 讀寫 | 開頭 | 否 | 否 |
w+ | 讀寫 | 開頭 | 是 | 是 |
a+ | 讀寫追加 | 結尾 | 否 | 是 |
fwrite(resource $handle, string $data, int $length = ?): int|false — 寫入字符串(二進制安全)fputs() — fwrite() 別名fread(resource $handle, int $length): string|false — 讀取指定長度字節fgets(resource $handle, int $length = ?): string|false — 讀取一行(含換行符)fgetc(resource $handle): string|false — 讀取單字符feof(resource $handle): bool — 判斷是否到達文件末尾fclose(resource $handle): bool — 關閉流,釋放資源(會自動刷新緩衝區)fflush(resource $handle): bool — 強制刷新輸出緩衝區<?php
$file = 'demo.txt';
$content = "Hello, PHP IO Stream!\n當前時間: " . date('Y-m-d H:i:s');
$fp = fopen($file, 'w'); // 覆蓋寫入
if ($fp === false) {
die("無法打開文件: $file");
}
$bytes = fwrite($fp, $content);
echo "成功寫入 $bytes 字節";
fclose($fp); // 必須關閉
?>
<?php
$file = 'demo.txt';
$fp = fopen($file, 'r');
if ($fp === false) {
die("文件不存在或不可讀: $file");
}
echo "<h3>文件內容:</h3>";
echo "<pre>";
while (!feof($fp)) {
$line = fgets($fp, 1024);
if ($line !== false) {
echo htmlspecialchars($line);
}
}
echo "</pre>";
fclose($fp);
?>
<?php
$src = 'logo.png';
$dest = 'logo_backup_' . date('Ymd') . '.png';
$srcFp = fopen($src, 'rb'); // 二進制讀取
$destFp = fopen($dest, 'wb'); // 二進制寫入
if (!$srcFp || !$destFp) {
die("文件句柄創建失敗");
}
$bufferSize = 8192; // 8KB 緩衝區
while (!feof($srcFp)) {
$chunk = fread($srcFp, $bufferSize);
fwrite($destFp, $chunk);
}
fclose($srcFp);
fclose($destFp);
echo "二進制文件複製完成: $dest";
?>
<?php
$data = [
['ID', '姓名', '郵箱', '註冊時間'],
[1, '張三', 'zhang@example.com', '2024-01-15'],
[2, '李四', 'li@example.com', '2024-02-20'],
[3, '王五', 'wang@example.com', '2024-03-10'],
];
$fp = fopen('users_export.csv', 'w');
// 寫入 BOM 防止 Excel 打開中文亂碼
fwrite($fp, "\xEF\xBB\xBF");
foreach ($data as $row) {
fputcsv($fp, $row); // 自動處理逗號、引號轉義
}
fclose($fp);
echo "CSV 導出成功,共 " . (count($data) - 1) . " 條記錄";
?>
<?php
$opts = [
'http' => [
'method' => 'GET',
'header' => "User-Agent: Mozilla/5.0 (compatible; PHP Bot)\r\n" .
"Accept: text/html,application/xhtml+xml\r\n",
'timeout' => 10,
'follow_location' => 1,
'max_redirects' => 3,
],
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
]
];
$context = stream_context_create($opts);
$content = file_get_contents('https://api.example.com/data', false, $context);
if ($content !== false) {
$data = json_decode($content, true);
print_r($data);
}
?>
<?php
// 註冊自定義過濾器:大寫轉換
class StrToUpperFilter extends php_user_filter {
public function filter($in, $out, &$consumed, $closing) {
while ($bucket = stream_bucket_make_writeable($in)) {
$bucket->data = strtoupper($bucket->data);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
stream_filter_register('str.toupper', 'StrToUpperFilter');
// 使用過濾器
$fp = fopen('input.txt', 'r');
stream_filter_append($fp, 'str.toupper');
while (!feof($fp)) {
echo fgets($fp);
}
fclose($fp);
?>
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 權限拒絕 | 目錄/文件無寫入權限 | 檢查 PHP 進程用戶權限,Linux 下 chown www:www /path |
| 大文件讀取記憶體溢出 | 一次性 file_get_contents() 大文件 | 改用 fread() 分塊讀取,或生成器逐行處理 |
| Windows 換行符混亂 | \r\n vs \n | 使用 fgets() 自動處理,或寫入前統一 PHP_EOL |
| 編碼亂碼 | 文件編碼與腳本編碼不一致 | 明確指定 mb_convert_encoding(),或使用 fopen($file, 'r', false, stream_context_create(['encoding'=>'UTF-8'])) |
| 句柄洩漏 | 異常分支未 fclose() | 使用 try/finally 或 register_shutdown_function() 保證關閉 |
PHP 5.1+ 提供面向對象的文件操作類,更優雅、安全:
<?php
$file = new SplFileObject('data.csv', 'r');
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY);
foreach ($file as $row) {
// $row 已自動解析為數組
printf("ID: %s, Name: %s, Email: %s\n", $row[0], $row[1], $row[2]);
}
// 寫入示例
$out = new SplFileObject('output.txt', 'w');
$out->fwrite("第一行\n");
$out->fwrite("第二行\n");
// 自動關閉(析構函數)
?>
掌握 PHP IO 流機制,熟練使用 fopen/fread/fwrite/fclose 核心族群,理解流模式、緩衝區、上下文與過濾器,是處理文件上傳、日誌分析、數據導入導出、爬蟲抓取等核心業務的基石。建議新項目優先考慮 SplFileObject 以獲得更好的可維護性與異常安全性。