在PHP开发中,文件写入是与文件读取同等重要的基础技能。无论是记录系统日志、生成配置文件、还是导出用户数据,都离不开对文件写入函数的熟练运用。PHP提供了fwrite()和file_put_contents()两个核心写入函数,它们各有适用场景,搭配不同的文件打开模式,能覆盖绝大多数项目开发中的写入需求。下面我们围绕这两个函数,结合真实可运行的代码示例,把文件写入这件事讲透。
一、写入前的准备:fopen()的文件打开模式
在往文件里写数据之前,需要先用fopen()打开或创建文件。这里要特别注意打开模式的选择,因为它直接决定了写入行为是“追加”还是“覆盖”,以及文件不存在时是否自动创建。
PHP中和写入相关的fopen()模式主要有以下几种:
| 模式 | 含义 | 文件不存在时的行为 |
|---|---|---|
w |
写入模式,清空已有内容后从头写入 | 尝试创建新文件 |
w+ |
读写模式,清空已有内容 | 尝试创建新文件 |
a |
追加模式,在文件末尾追加数据 | 尝试创建新文件 |
a+ |
追加读写模式 | 尝试创建新文件 |
x |
排他创建模式,仅用于新建文件 | 文件已存在则返回FALSE并报错 |
x+ |
排他创建读写模式 | 文件已存在则返回FALSE并报错 |
c |
写入模式,不清空已有内容,指针指向开头 | 尝试创建新文件 |
c+ |
读写模式,不清空已有内容 | 尝试创建新文件 |
个人经验之谈:初学PHP文件操作时,我一度搞不清楚w和c的区别,结果写了一个缓存系统,每次请求都用w模式打开缓存文件,把之前缓存的数据全清空了,导致缓存命中率为零。实际上,w模式会在打开文件时立即把文件截断为零长度,而c模式不会自动清空,只是把内部指针放到文件开头。如果你需要在已有文件的开头位置插入或覆盖特定字节,c模式会很有用;而绝大多数普通写入场景,w(覆盖)和a(追加)已经够用了。
用fopen()新建一个文件
<?php
// 以写入模式打开,文件不存在时会自动创建
$fileHandle = fopen("storage/data_cache.txt", "w");
fclose($fileHandle);
?>
执行完这段代码后,storage/目录下就会多出一个空的data_cache.txt文件。把这段代码放进项目里测试时,记得确认PHP对该目录有写入权限,不然会报“failed to open stream”的错误。
二、fwrite():配合文件句柄进行写入
fwrite()是PHP里比价底层的文件写入函数,使用时必须先通过fopen()拿到一个有效的文件资源句柄。函数本身简单直接——往打开的文件里写入指定字符串,写入完成后返回成功写入的字节数。
语法结构
int fwrite ( resource $handle , string $string [, int $length ] )
-
$handle:由fopen()返回的文件句柄。 -
$string:要写入的内容字符串。 -
$length:可选参数,限制这次写入的较大字节数。如果$string的长度超过$length,只会写入前$length个字节。
示例1:基本写入操作
<?php
$fp = fopen("storage/user_login_log.txt", "w");
$log_entry = "2026-05-11 | 用户ID 2088 登录成功 | IP 10.0.2.33\n";
$bytes_written = fwrite($fp, $log_entry);
echo "本次写入字节数:" . $bytes_written;
fclose($fp);
?>
执行后文件内容
2026-05-11 | 用户ID 2088 登录成功 | IP 10.0.2.33
这里返回的写入字节数在项目中很有用——如果返回值与预期不符,说明写入过程中可能出现了问题,可以在代码里做相应的异常判断。
示例2:带字节数限制的写入
<?php
$fp = fopen("storage/temp_output.txt", "w");
$content = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 只写入前8个字符
fwrite($fp, $content, 8);
fclose($fp);
?>
执行后文件内容
ABCDEFGH
$length参数在某些网络流操作中会派上用场,比如通过socket传输数据时分批发送。
示例3:追加模式写入
<?php
// 第一次写入
$fp = fopen("storage/daily_quotes.txt", "w");
fwrite($fp, "实践是检验真理的唯一标准。\n");
fclose($fp);
// 第二次写入,使用a模式追加
$fp = fopen("storage/daily_quotes.txt", "a");
fwrite($fp, "千里之行,始于足下。\n");
fclose($fp);
?>
执行后文件内容
实践是检验真理的唯一标准。
千里之行,始于足下。
关于fwrite()有一个值得注意的同名函数——fputs()。实际上fputs()就是fwrite()的别名,两者功能一样,参数也一致。C语言背景的开发者可能更习惯用fputs这个名字,但在PHP社区里fwrite更常见,看自己的编码习惯选择即可。
三、file_put_contents():一步到位的写入方式
如果说fwrite()是手动挡,那file_put_contents()就是自动挡。它把文件打开、写入、关闭这三步操作封装到一个函数里,代码量大幅减少。对于中小型文件的一次性写入,这个函数通常是优先选择。
语法结构
int file_put_contents ( string $filename , mixed $data [, int $flags = 0 [, resource $context ]] )
-
$filename:目标文件路径,不存在时会尝试创建。 -
$data:要写入的数据,可以是字符串、一维数组,甚至是流资源。 -
$flags:可选标志位,控制写入行为。 -
$context:可选,用于修改流行为的上下文资源。
常用的flags标志位
-
FILE_APPEND:在文件末尾追加数据,不清空原有内容。 -
LOCK_EX:写入时获取文件排他锁,防止并发写入导致数据错乱。 -
FILE_USE_INCLUDE_PATH:在include_path目录中搜索文件。
示例1:基本写入
<?php
$result = file_put_contents("storage/version_info.txt", "当前版本:v2.8.3\n发布日期:2026-05-11\n");
echo "写入结果:" . $result . " 字节";
?>
执行后文件内容
当前版本:v2.8.3
发布日期:2026-05-11
示例2:追加模式写入
<?php
// 先写入初始内容
file_put_contents("storage/update_log.txt", "[2026-05-10] 系统初始化部署完成\n");
// 追加新记录,注意加上FILE_APPEND标志
file_put_contents("storage/update_log.txt", "[2026-05-11] 修复用户头像上传问题\n", FILE_APPEND);
?>
执行后文件内容
[2026-05-10] 系统初始化部署完成
[2026-05-11] 修复用户头像上传问题
如果不加FILE_APPEND,第二次调用会覆盖第一次的内容,这是很多新手容易忽略的细节。
示例3:结合LOCK_EX防止并发写入冲突
<?php
$visitor_data = "访客IP: 192.168.1.50 | 访问时间: 2026-05-11\n";
file_put_contents("storage/site_visits.txt", $visitor_data, FILE_APPEND | LOCK_EX);
?>
在高并发场景下(如多个请求同时写入同一个计数文件),加上LOCK_EX标志能确保同一时刻只有一个进程在写入,避免数据交叉覆盖。
四、覆盖写入与追加写入的对比
理解“覆盖”和“追加”的区别,是掌握PHP文件写入的关键节点。用w模式或file_put_contents()不加FILE_APPEND,每次写入都会清空文件原有内容;用a模式或加上FILE_APPEND,内容会被添加到文件末尾。
覆盖写入示例
<?php
$fp = fopen("storage/status_report.txt", "w");
fwrite($fp, "第一轮数据写入\n");
fclose($fp);
// 以w模式再次打开并写入
$fp = fopen("storage/status_report.txt", "w");
fwrite($fp, "第二轮数据写入(覆盖了第一轮)\n");
fclose($fp);
?>
执行后文件内容
第二轮数据写入(覆盖了第一轮)
可以看到,第一次写入的内容被清空替换。
个人建议:在记录错误日志这种需要长期累积数据的场景,务必使用a追加模式或FILE_APPEND标志。我刚接触PHP时,用w模式写了个简单的错误日志系统,结果每次新错误都把旧日志清掉了,排查线上问题时只能看到最近的报错记录,吃了不小的亏。如果是写入重要的业务数据,建议在写入前后加上文件锁(可以组合使用flock()函数),并在写入完成后校验文件内容的正确性。
五、fwrite()与file_put_contents()的选型思路
可能有人会问:既然file_put_contents()这么方便,为什么还要用fwrite()?这其实涉及到一个粒度控制的问题。
用fwrite()更合适的情况:
-
需要多次、分批往同一个文件句柄写入数据(比如循环写入大量行)。
-
需要精确控制文件指针位置(配合
fseek()使用)。 -
需要更细粒度的锁控制,配合
flock()做复杂的事务性写入。
用file_put_contents()更合适的情况:
-
一次性写入完整内容,代码简洁优先。
-
写入频率不高的小型数据文件。
-
需要结合
LOCK_EX做简单的并发写入保护。
我的实战经验:写数据导出功能时,如果导出的是几万行以上的CSV文件,我会用fopen()+fwrite()逐行写入,不会先把所有数据拼成一个大字符串再用file_put_contents(),因为后者在数据量大时会占用大量内存。反过来,如果只是保存用户提交的一段配置JSON到文件,file_put_contents()一行代码搞定,比fopen()、fwrite()、fclose()三连调用省事多了,代码可读性也更好。
本节课程知识要点
-
进行文件写入操作前,务必确认
fopen()的打开模式是w(覆盖)、a(追加)还是c(不清空自由定位),模式选错会导致数据丢失或写入位置偏移。 -
fwrite()通过文件句柄写入,返回实际写入字节数,适合需要分批写入或精确控制指针的场景。 -
fputs()是fwrite()的别名,功能一致,可按个人编码习惯选用。 -
file_put_contents()封装了打开、写入、关闭全流程,适合一次性写入,搭配FILE_APPEND和LOCK_EX标志使用更安全。 -
追加写入用
FILE_APPEND或a模式,覆盖写入用w模式或去掉追加标志,这是决定数据留存还是被清空的核心开关。 -
处理高并发写入时,结合文件锁机制(
LOCK_EX或flock())能避免数据交叉覆盖的问题。 -
大数据量写入优先使用流式写入(
fopen()+fwrite()循环),避免将所有数据一次性加载到内存中。