PHP fflush() 函数
说明: fflush() 函数用于将缓冲内容输出到文件,强制将缓冲区的数据写入文件系统。
语法
bool fflush ( resource $stream )
参数说明
| 参数 |
描述 |
必需 |
| stream |
由 fopen() 打开的文件指针 |
是 |
返回值
工作原理
文件缓冲机制:
- 缓冲区:操作系统和标准库为了提高性能,会对文件写入进行缓冲
- 延迟写入:数据不会立即写入磁盘,而是先存储在内存缓冲区
- fflush()的作用:强制将缓冲区中的数据写入磁盘
- 自动刷新:缓冲区满、文件关闭或程序正常结束时,缓冲区会自动刷新
为什么需要fflush()? 在需要确保数据立即写入磁盘的场景(如日志记录、实时数据存储)中,fflush()可以防止数据丢失。
注意事项
- fflush() 只能用于可写的文件流
- 在某些操作系统或文件系统上,fflush() 可能不会立即将数据写入物理磁盘
- 过度使用 fflush() 会影响性能,因为它会频繁进行磁盘I/O操作
- 网络流(如HTTP、FTP)通常有自己的缓冲机制,fflush() 的行为可能不同
- fflush() 不会刷新 PHP 的输出缓冲(ob_flush() 用于此目的)
- 文件必须以写入模式打开(如 "w"、"a"、"r+"、"w+"、"a+" 等)
示例
示例 1:基本使用 - 确保数据写入
<?php
// 打开文件进行写入
$filename = "log.txt";
$handle = fopen($filename, "a");
if ($handle) {
echo "文件打开成功,开始写入数据...\n";
// 写入数据
$log_entry = "[" . date('Y-m-d H:i:s') . "] 用户登录\n";
fwrite($handle, $log_entry);
echo "数据写入缓冲区完成\n";
// 检查文件大小(可能还没有更新)
$size_before = filesize($filename);
echo "刷新前文件大小: " . $size_before . " 字节\n";
// 强制刷新缓冲区到磁盘
if (fflush($handle)) {
echo "✅ 缓冲区已刷新到磁盘\n";
// 清除文件状态缓存
clearstatcache();
// 检查文件大小(应该已更新)
$size_after = filesize($filename);
echo "刷新后文件大小: " . $size_after . " 字节\n";
if ($size_after > $size_before) {
echo "✅ 确认数据已写入磁盘\n";
}
} else {
echo "❌ 缓冲区刷新失败\n";
}
// 关闭文件
fclose($handle);
} else {
echo "无法打开文件: " . $filename . "\n";
}
// 清理测试文件
unlink($filename);
?>
示例 2:实时日志记录系统
<?php
/**
* 实时日志记录器类
* 使用fflush()确保日志立即写入磁盘
*/
class RealtimeLogger {
private $handle;
private $filename;
private $autoFlush;
public function __construct($filename, $autoFlush = true) {
$this->filename = $filename;
$this->autoFlush = $autoFlush;
// 以追加模式打开文件
$this->handle = fopen($filename, "a");
if (!$this->handle) {
throw new Exception("无法打开日志文件: " . $filename);
}
// 写入日志文件头
fwrite($this->handle, "=== 日志开始于 " . date('Y-m-d H:i:s') . " ===\n");
if ($this->autoFlush) {
fflush($this->handle);
}
}
/**
* 写入日志并可选地刷新缓冲区
*/
public function log($message, $level = "INFO") {
$timestamp = date('Y-m-d H:i:s');
$log_entry = sprintf("[%s] [%s] %s\n", $timestamp, $level, $message);
$bytes_written = fwrite($this->handle, $log_entry);
if ($bytes_written === false) {
return false;
}
// 如果启用自动刷新,立即写入磁盘
if ($this->autoFlush) {
return fflush($this->handle);
}
return true;
}
/**
* 手动刷新缓冲区
*/
public function flush() {
if ($this->handle) {
return fflush($this->handle);
}
return false;
}
/**
* 关闭日志文件
*/
public function close() {
if ($this->handle) {
// 写入结束标记
fwrite($this->handle, "=== 日志结束于 " . date('Y-m-d H:i:s') . " ===\n\n");
fflush($this->handle);
fclose($this->handle);
$this->handle = null;
}
}
public function __destruct() {
$this->close();
}
/**
* 获取日志文件统计信息
*/
public function getStats() {
if (!file_exists($this->filename)) {
return false;
}
clearstatcache();
return [
'filename' => $this->filename,
'size' => filesize($this->filename),
'modified' => date('Y-m-d H:i:s', filemtime($this->filename)),
'is_writable' => is_writable($this->filename)
];
}
}
// 使用示例
echo "实时日志记录器演示:\n";
echo "========================\n";
try {
// 创建日志记录器(启用自动刷新)
$logger = new RealtimeLogger("app.log", true);
// 记录一些事件
$events = [
["用户登录成功", "INFO"],
["数据库连接已建立", "DEBUG"],
["处理用户请求 #1001", "INFO"],
["警告:内存使用率较高", "WARNING"],
["错误:数据库查询失败", "ERROR"]
];
foreach ($events as $event) {
list($message, $level) = $event;
$logger->log($message, $level);
echo "已记录: [$level] $message\n";
// 模拟实时性 - 每0.5秒记录一条
usleep(500000);
}
// 手动刷新(如果自动刷新已启用,这一步是多余的)
$logger->flush();
// 获取统计信息
$stats = $logger->getStats();
if ($stats) {
echo "\n日志文件统计:\n";
echo "文件名: " . $stats['filename'] . "\n";
echo "大小: " . $stats['size'] . " 字节\n";
echo "最后修改: " . $stats['modified'] . "\n";
echo "可写: " . ($stats['is_writable'] ? '是' : '否') . "\n";
}
// 自动关闭(通过析构函数)
unset($logger);
// 显示日志内容
echo "\n日志文件内容:\n";
echo file_get_contents("app.log");
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
// 清理
if (file_exists("app.log")) {
unlink("app.log");
}
?>
示例 3:性能测试 - 有缓冲 vs 无缓冲
<?php
/**
* 文件写入性能测试
* 比较有缓冲和无缓冲(使用fflush)的性能差异
*/
function file_write_performance_test($filename, $iterations = 1000, $data_size = 1024) {
$results = [];
// 测试数据
$test_data = str_repeat('A', $data_size);
echo "文件写入性能测试\n";
echo "================\n";
echo "测试文件: $filename\n";
echo "迭代次数: $iterations\n";
echo "每次写入: $data_size 字节\n";
echo "总数据量: " . ($iterations * $data_size) . " 字节\n\n";
// 测试1:使用缓冲(默认行为)
echo "测试1: 使用缓冲(默认)\n";
$handle1 = fopen($filename . "_buffered.txt", "w");
$start1 = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
fwrite($handle1, $test_data);
}
fclose($handle1);
$end1 = microtime(true);
$time1 = $end1 - $start1;
echo "时间: " . round($time1, 4) . " 秒\n";
echo "速度: " . round(($iterations * $data_size) / $time1 / 1024, 2) . " KB/秒\n\n";
// 测试2:每次写入后刷新(无缓冲)
echo "测试2: 每次写入后刷新(无缓冲)\n";
$handle2 = fopen($filename . "_unbuffered.txt", "w");
$start2 = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
fwrite($handle2, $test_data);
fflush($handle2); // 每次写入后刷新
}
fclose($handle2);
$end2 = microtime(true);
$time2 = $end2 - $start2;
echo "时间: " . round($time2, 4) . " 秒\n";
echo "速度: " . round(($iterations * $data_size) / $time2 / 1024, 2) . " KB/秒\n\n";
// 测试3:每N次写入后刷新
echo "测试3: 每10次写入后刷新\n";
$handle3 = fopen($filename . "_batch.txt", "w");
$start3 = microtime(true);
$flush_interval = 10;
for ($i = 0; $i < $iterations; $i++) {
fwrite($handle3, $test_data);
if ($i % $flush_interval == 0) {
fflush($handle3);
}
}
fclose($handle3);
$end3 = microtime(true);
$time3 = $end3 - $start3;
echo "时间: " . round($time3, 4) . " 秒\n";
echo "速度: " . round(($iterations * $data_size) / $time3 / 1024, 2) . " KB/秒\n\n";
// 清理测试文件
unlink($filename . "_buffered.txt");
unlink($filename . "_unbuffered.txt");
unlink($filename . "_batch.txt");
return [
'buffered' => $time1,
'unbuffered' => $time2,
'batch' => $time3,
'recommendation' => $time1 < $time2 ? '使用缓冲' : '使用无缓冲'
];
}
// 运行性能测试
$results = file_write_performance_test("test_perf", 100, 512);
echo "性能对比总结:\n";
echo "==============\n";
echo "缓冲写入速度最快: " . ($results['recommendation'] == '使用缓冲' ? '是' : '否') . "\n";
echo "缓冲 vs 无缓冲时间比: " . round($results['buffered'] / $results['unbuffered'], 2) . "\n";
echo "建议: " . $results['recommendation'] . "\n";
echo "\n注意: 实际性能取决于磁盘速度、操作系统和文件系统\n";
?>
示例 4:事务性文件写入
<?php
/**
* 事务性文件写入类
* 确保数据要么完全写入,要么完全不写入
*/
class TransactionalFileWriter {
private $tempHandle;
private $tempFilename;
private $targetFilename;
private $committed = false;
public function __construct($targetFilename) {
$this->targetFilename = $targetFilename;
// 创建临时文件
$this->tempFilename = tempnam(sys_get_temp_dir(), 'txn_');
$this->tempHandle = fopen($this->tempFilename, "w");
if (!$this->tempHandle) {
throw new Exception("无法创建临时文件");
}
}
/**
* 写入数据到临时文件
*/
public function write($data) {
if ($this->committed) {
throw new Exception("事务已提交,不能继续写入");
}
$bytes = fwrite($this->tempHandle, $data);
// 立即刷新以确保数据写入临时文件
if ($bytes !== false) {
fflush($this->tempHandle);
}
return $bytes;
}
/**
* 提交事务 - 将临时文件移动到目标文件
*/
public function commit() {
if ($this->committed) {
throw new Exception("事务已提交");
}
// 关闭临时文件
fclose($this->tempHandle);
// 使用原子操作重命名文件
if (rename($this->tempFilename, $this->targetFilename)) {
$this->committed = true;
// 确保文件系统同步
$handle = fopen($this->targetFilename, "r");
if ($handle) {
fflush($handle);
fclose($handle);
}
return true;
}
return false;
}
/**
* 回滚事务 - 删除临时文件
*/
public function rollback() {
if ($this->tempHandle) {
fclose($this->tempHandle);
}
if (file_exists($this->tempFilename)) {
unlink($this->tempFilename);
}
$this->tempHandle = null;
$this->tempFilename = null;
$this->committed = false;
}
public function __destruct() {
if (!$this->committed) {
$this->rollback();
}
}
/**
* 获取事务状态
*/
public function getStatus() {
$tempSize = file_exists($this->tempFilename) ? filesize($this->tempFilename) : 0;
return [
'target_file' => $this->targetFilename,
'temp_file' => $this->tempFilename,
'committed' => $this->committed,
'temp_size' => $tempSize,
'target_exists' => file_exists($this->targetFilename)
];
}
}
// 使用示例
echo "事务性文件写入演示:\n";
echo "===================\n";
try {
// 创建事务性写入器
$writer = new TransactionalFileWriter("important_data.txt");
echo "1. 事务状态:\n";
print_r($writer->getStatus());
// 写入一些数据
$data = [
"第一行重要数据\n",
"第二行重要数据\n",
"第三行重要数据\n"
];
echo "\n2. 写入数据到临时文件...\n";
foreach ($data as $line) {
$bytes = $writer->write($line);
echo " 写入 " . $bytes . " 字节: " . trim($line) . "\n";
}
echo "\n3. 提交前的事务状态:\n";
$status_before = $writer->getStatus();
echo " 临时文件大小: " . $status_before['temp_size'] . " 字节\n";
echo " 目标文件存在: " . ($status_before['target_exists'] ? '是' : '否') . "\n";
echo "\n4. 提交事务...\n";
if ($writer->commit()) {
echo " ✅ 事务提交成功\n";
} else {
echo " ❌ 事务提交失败\n";
}
echo "\n5. 提交后的事务状态:\n";
$status_after = $writer->getStatus();
print_r($status_after);
// 显示最终文件内容
echo "\n6. 最终文件内容:\n";
if (file_exists("important_data.txt")) {
echo file_get_contents("important_data.txt");
}
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
// 清理测试文件
if (file_exists("important_data.txt")) {
unlink("important_data.txt");
}
echo "\n\n演示回滚场景:\n";
echo "===================\n";
try {
$writer2 = new TransactionalFileWriter("rolled_back.txt");
$writer2->write("这条数据应该被回滚\n");
echo "写入数据后,但不提交...\n";
// 显式回滚
$writer2->rollback();
echo "事务已回滚\n";
$status = $writer2->getStatus();
echo "回滚后状态:\n";
echo "目标文件存在: " . ($status['target_exists'] ? '是' : '否') . "\n";
echo "临时文件存在: " . (file_exists($status['temp_file'] ?? '') ? '是' : '否') . "\n";
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
?>
示例 5:网络流中的fflush()使用
<?php
/**
* 网络流处理类
* 演示在网络流中使用fflush()
*/
class NetworkStreamHandler {
private $handle;
private $url;
private $timeout = 10;
public function __construct($url, $timeout = 10) {
$this->url = $url;
$this->timeout = $timeout;
}
/**
* 连接到远程服务器并发送数据
*/
public function connectAndSend($data) {
// 创建流上下文
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\n" .
"Connection: close\r\n",
'content' => http_build_query(['data' => $data]),
'timeout' => $this->timeout
]
]);
// 打开网络流
$this->handle = fopen($this->url, 'r', false, $context);
if (!$this->handle) {
throw new Exception("无法连接到: " . $this->url);
}
return $this->handle;
}
/**
* 发送数据并立即刷新
*/
public function sendWithFlush($data) {
// 注意:对于HTTP流,fflush()的行为可能不同
// 通常需要在发送数据后使用
$result = $this->connectAndSend($data);
if ($result) {
// 尝试刷新(在某些流实现中可能有效)
$flushed = fflush($this->handle);
// 读取响应
$response = stream_get_contents($this->handle);
fclose($this->handle);
$this->handle = null;
return [
'flushed' => $flushed,
'response' => $response
];
}
return false;
}
/**
* 演示TCP套接字连接(需要socket扩展)
*/
public function tcpSend($host, $port, $data) {
// 创建TCP套接字连接
$this->handle = fsockopen($host, $port, $errno, $errstr, $this->timeout);
if (!$this->handle) {
throw new Exception("无法连接到 $host:$port - $errstr ($errno)");
}
// 设置非阻塞模式(可选)
stream_set_blocking($this->handle, false);
// 发送数据
fwrite($this->handle, $data . "\n");
// 立即刷新,确保数据发送
$flushed = fflush($this->handle);
// 等待响应
$response = '';
$start = time();
while ((time() - $start) < $this->timeout) {
$chunk = fread($this->handle, 1024);
if ($chunk !== false && strlen($chunk) > 0) {
$response .= $chunk;
} else {
break;
}
}
fclose($this->handle);
$this->handle = null;
return [
'flushed' => $flushed,
'response' => $response,
'data_sent' => $data
];
}
}
// 使用示例(演示模式)
echo "网络流fflush()演示\n";
echo "==================\n\n";
echo "1. HTTP流演示(模拟):\n";
echo " 注意:HTTP流通常有自己的缓冲机制\n";
echo " fflush()在某些HTTP流实现中可能不会立即发送数据\n\n";
echo "2. TCP套接字演示(模拟):\n";
try {
$handler = new NetworkStreamHandler("http://example.com", 5);
// 模拟TCP发送
echo " 模拟发送数据到服务器...\n";
// 注意:实际运行需要真实的服务器地址
// 这里只是演示代码结构
/*
$result = $handler->tcpSend("127.0.0.1", 8080, "Hello Server");
if ($result) {
echo " 刷新状态: " . ($result['flushed'] ? '成功' : '失败') . "\n";
echo " 响应: " . substr($result['response'], 0, 100) . "...\n";
}
*/
echo " 由于需要真实服务器连接,此处跳过实际执行\n";
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
echo "\n重要提示:\n";
echo "对于网络流,fflush()的行为取决于:\n";
echo "1. 流类型(HTTP、FTP、TCP等)\n";
echo "2. PHP配置和流包装器\n";
echo "3. 操作系统和网络设置\n";
echo "4. 服务器端的缓冲策略\n";
?>
示例 6:多进程/多线程环境下的文件同步
<?php
/**
* 多进程安全的文件写入器
* 使用文件锁和fflush()确保多进程环境下的数据一致性
*/
class MultiprocessFileWriter {
private $filename;
private $lockHandle;
private $writeHandle;
public function __construct($filename) {
$this->filename = $filename;
}
/**
* 安全写入数据(使用文件锁)
*/
public function safeWrite($data, $timeout = 5) {
$startTime = microtime(true);
$locked = false;
// 尝试获取锁
while ((microtime(true) - $startTime) < $timeout) {
$this->lockHandle = fopen($this->filename . '.lock', 'w');
if (flock($this->lockHandle, LOCK_EX | LOCK_NB)) {
$locked = true;
break;
}
fclose($this->lockHandle);
usleep(100000); // 等待100ms后重试
}
if (!$locked) {
throw new Exception("获取文件锁超时");
}
try {
// 打开文件进行写入
$this->writeHandle = fopen($this->filename, 'a');
if (!$this->writeHandle) {
throw new Exception("无法打开文件: " . $this->filename);
}
// 写入数据
$bytes = fwrite($this->writeHandle, $data);
if ($bytes !== false) {
// 立即刷新到磁盘
fflush($this->writeHandle);
// 强制文件系统同步(如果支持)
if (function_exists('fsync')) {
fsync($this->writeHandle);
}
}
fclose($this->writeHandle);
return $bytes;
} finally {
// 释放锁
flock($this->lockHandle, LOCK_UN);
fclose($this->lockHandle);
// 删除锁文件
if (file_exists($this->filename . '.lock')) {
unlink($this->filename . '.lock');
}
}
}
/**
* 批量写入多条数据
*/
public function batchWrite($dataArray) {
$result = [];
foreach ($dataArray as $index => $data) {
try {
$bytes = $this->safeWrite($data . "\n");
$result[$index] = [
'success' => true,
'bytes' => $bytes
];
} catch (Exception $e) {
$result[$index] = [
'success' => false,
'error' => $e->getMessage()
];
}
}
return $result;
}
/**
* 模拟多进程写入测试
*/
public static function simulateMultiprocessTest($filename, $processCount = 3, $writesPerProcess = 5) {
echo "多进程文件写入测试\n";
echo "进程数: $processCount, 每个进程写入次数: $writesPerProcess\n\n";
$results = [];
// 创建多个"进程"(实际上是顺序执行模拟)
for ($i = 1; $i <= $processCount; $i++) {
echo "进程 $i 开始写入...\n";
$writer = new MultiprocessFileWriter($filename);
$data = [];
for ($j = 1; $j <= $writesPerProcess; $j++) {
$message = "进程 $i, 写入 #$j, 时间: " . microtime(true);
$data[] = $message;
}
$result = $writer->batchWrite($data);
$results[$i] = $result;
echo "进程 $i 完成写入\n";
}
// 验证结果
$allLines = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$lineCount = count($allLines);
$expectedCount = $processCount * $writesPerProcess;
echo "\n验证结果:\n";
echo "预期行数: $expectedCount\n";
echo "实际行数: $lineCount\n";
if ($lineCount === $expectedCount) {
echo "✅ 所有写入成功,数据一致\n";
} else {
echo "❌ 数据不一致,可能发生了数据丢失或竞争条件\n";
// 检查重复或丢失的行
$uniqueLines = array_unique($allLines);
$duplicateCount = $lineCount - count($uniqueLines);
echo "重复行数: $duplicateCount\n";
}
// 清理测试文件
if (file_exists($filename)) {
unlink($filename);
}
return $results;
}
}
// 运行多进程测试
echo "多进程文件同步演示:\n";
echo "==================\n\n";
MultiprocessFileWriter::simulateMultiprocessTest("multiprocess_test.log", 3, 5);
echo "\n实际应用场景:\n";
echo "1. 多进程日志记录\n";
echo "2. 数据采集系统\n";
echo "3. 并行处理任务的结果汇总\n";
echo "4. 实时监控系统\n";
?>
fflush() vs 相关函数
| 函数 |
作用 |
适用场景 |
区别 |
fflush() |
刷新文件缓冲区到磁盘 |
文件写入、确保数据持久化 |
只刷新文件流缓冲区 |
ob_flush() |
刷新输出缓冲区 |
Web输出、实时显示数据 |
刷新PHP输出缓冲区,不是文件缓冲区 |
fsync() |
强制将文件数据写入磁盘 |
需要最高级别的数据安全 |
比fflush()更强力,确保数据写入物理磁盘 |
fclose() |
关闭文件 |
文件操作结束 |
关闭文件时会自动刷新缓冲区 |
clearstatcache() |
清除文件状态缓存 |
获取最新文件信息 |
不刷新缓冲区,只清除缓存信息 |
相关函数
- 权衡性能与安全性:频繁使用fflush()会影响性能,只在必要时使用
- 关键数据立即刷新:对于日志、事务数据等关键信息,使用fflush()确保数据不丢失
- 结合文件锁使用:在多进程/多线程环境中,结合flock()使用fflush()
- 考虑使用批处理:不要每次写入都刷新,可以每N次写入刷新一次
- 错误处理:检查fflush()的返回值,处理刷新失败的情况
- 网络流谨慎使用:网络流的fflush()行为可能不同,需要测试验证
- 配合fsync()使用:对于最高级别的数据安全,可以结合使用fflush()和fsync()
- 清理状态缓存:刷新后使用clearstatcache()获取最新的文件信息
模式1:关键数据写入
// 写入关键数据并立即刷新
$handle = fopen($filename, "a");
fwrite($handle, $critical_data);
fflush($handle); // 确保数据写入磁盘
fclose($handle);
模式2:定期刷新(批处理)
// 每N次写入刷新一次
$handle = fopen($filename, "a");
$counter = 0;
$flush_interval = 100;
foreach ($data_items as $item) {
fwrite($handle, $item);
$counter++;
if ($counter % $flush_interval == 0) {
fflush($handle); // 定期刷新
}
}
fclose($handle);
模式3:事务性写入
// 使用临时文件和原子操作
$temp_file = tempnam(sys_get_temp_dir(), 'tmp_');
$handle = fopen($temp_file, "w");
// 写入所有数据
foreach ($data as $chunk) {
fwrite($handle, $chunk);
}
fflush($handle); // 确保所有数据写入临时文件
fclose($handle);
// 原子性地重命名到目标文件
rename($temp_file, $target_file);