PHP 文件操作

PHP 提供了完整的文件系统操作函数,可以读写文件、管理目录。文件操作是后端开发中非常常见的需求,无论是读取配置文件、写入日志,还是处理上传的 CSV/JSON 数据,都离不开这些基础能力。

读取文件

PHP 提供了多种读取文件的方式,可以根据文件大小和使用场景灵活选择。对于小文件,file_get_contents() 最为简便;对于大文件,推荐使用 fopen() 逐行读取以节省内存。

PHP 实例
// 一次性读取整个文件
$content = file_get_contents('data.txt');
echo $content;

// 读取为数组(每行一个元素) $lines = file('data.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { echo $line . "\n"; }

// 逐行读取大文件(节省内存) $handle = fopen('large.txt', 'r'); while (!feof($handle)) { $line = fgets($handle); echo $line; } fclose($handle); ``

file_get_contents() 还支持读取远程 URL,但生产环境建议使用 cURL 或 Guzzle 以获得更好的错误控制。使用 fopen() 时务必记得调用 fclose() 释放文件句柄,否则可能导致资源泄漏。

写入文件

写入文件同样有多种方式。file_put_contents() 是最简洁的选择,适合一次性写入;fopen() + fwrite() 则适合需要多次写入或精细控制的场景。

`php // 覆盖写入 file_put_contents('output.txt', "Hello, World!\n");

// 追加写入 file_put_contents('log.txt', date('Y-m-d H:i:s') . " 操作日志\n", FILE_APPEND);

// 使用 fopen 写入 $handle = fopen('data.txt', 'w'); // w=覆盖, a=追加, r=只读 fwrite($handle, "第一行\n"); fwrite($handle, "第二行\n"); fclose($handle); `

FILE_APPEND 标志位可以让 file_put_contents() 以追加模式写入,非常适合日志记录场景。高并发环境下写入应配合 LOCK_EX 标志位加锁,防止多个进程同时写入导致数据混乱。

文件模式说明

模式说明
r只读,指针在开头
w只写,清空文件或创建新文件
a追加,指针在末尾
r+读写,指针在开头
w+读写,清空文件
x创建并写入,文件已存在则失败

文件信息

在操作文件之前,通常需要先检查文件是否存在、是否可读写,以及获取文件的基本元信息。PHP 提供了一系列函数来完成这些检查,合理使用可以避免很多运行时错误。

`php $file = 'example.txt';

echo file_exists($file) ? "存在" : "不存在"; echo is_file($file) ? "是文件" : ""; echo is_readable($file) ? "可读" : ""; echo is_writable($file) ? "可写" : "";

echo filesize($file); // 文件大小(字节) echo date('Y-m-d', filemtime($file)); // 最后修改时间 echo pathinfo($file, PATHINFO_EXTENSION); // 扩展名 echo basename($file); // 文件名 echo dirname($file); // 目录路径 echo realpath($file); // 绝对路径 `

pathinfo() 函数非常实用,除了获取扩展名,还可以通过 PATHINFO_FILENAME 获取不含扩展名的文件名。realpath() 会解析符号链接并返回规范化的绝对路径,在处理用户上传路径时可用于防止路径穿越攻击。

文件与目录管理

PHP 内置了完整的目录操作函数,可以创建、删除、遍历目录。对于需要递归遍历目录树的场景,RecursiveDirectoryIterator 是比手写递归更优雅的选择。

`php // 复制、移动、删除 copy('source.txt', 'dest.txt'); rename('old.txt', 'new.txt'); // 也可用于移动文件 unlink('delete.txt'); // 删除文件

// 目录操作 mkdir('new_dir', 0755, true); // 递归创建目录 rmdir('empty_dir'); // 删除空目录 $files = scandir('.'); // 列出目录内容

// 递归列出所有文件 $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator('./articles') ); foreach ($iterator as $file) { if ($file->isFile()) { echo $file->getPathname() . "\n"; } } `

mkdir() 的第三个参数 true 表示递归创建,即使中间目录不存在也会一并创建,类似 Linux 的 mkdir -prename() 不仅可以重命名文件,还可以将文件移动到不同目录。

CSV 文件处理

CSV 是数据交换中最常见的格式之一,PHP 内置了 fputcsv()fgetcsv() 专门处理 CSV 文件,能自动处理字段中的逗号、引号和换行符等特殊字符。

`php // 写入 CSV $data = [ ["姓名", "年龄", "城市"], ["张三", 25, "北京"], ["李四", 30, "上海"], ];

$handle = fopen('users.csv', 'w'); foreach ($data as $row) { fputcsv($handle, $row); } fclose($handle);

// 读取 CSV $handle = fopen('users.csv', 'r'); while (($row = fgetcsv($handle)) !== false) { echo implode(', ', $row) . "\n"; } fclose($handle); `

处理中文 CSV 时需要注意编码问题,Excel 默认使用 GBK 编码,如果需要兼容 Excel 打开,可以在写入前加上 BOM 头:fwrite($handle, "\xEF\xBB\xBF")

JSON 文件处理

JSON 是现代应用中最常用的配置和数据交换格式。PHP 的 json_encode()json_decode() 函数使得 JSON 处理非常简单,配合文件读写函数即可完成 JSON 文件的读写。

`php // 写入 JSON $config = [ 'debug' => false, 'version' => '1.0.0', 'database' => ['host' => 'localhost', 'port' => 3306], ]; file_put_contents('config.json', json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));

// 读取 JSON $json = file_get_contents('config.json'); $config = json_decode($json, true); // true 返回数组,false 返回对象 echo $config['version']; // 1.0.0 `

JSON_PRETTY_PRINT 会让输出的 JSON 带有缩进,便于人工阅读。JSON_UNESCAPED_UNICODE 确保中文字符不被转义为 \uXXXX 形式。读取后务必检查 json_last_error() 是否为 JSON_ERROR_NONE

文件锁(并发安全)

在多进程或高并发环境下,多个请求可能同时读写同一个文件,导致数据竞争问题。PHP 的 flock() 函数提供了文件锁机制,可以有效防止并发冲突。

`php $handle = fopen('counter.txt', 'c+');

if (flock($handle, LOCK_EX)) { // 独占锁 $count = (int)fread($handle, 100); $count++; rewind($handle); fwrite($handle, $count); ftruncate($handle, ftell($handle)); flock($handle, LOCK_UN); // 释放锁 }

fclose($handle);

LOCK_EX 是独占锁(写锁),同一时间只有一个进程可以持有;LOCK_SH 是共享锁(读锁),多个进程可以同时持有。在实际项目中,对于高并发计数器场景,更推荐使用 Redis 的原子操作来替代文件锁。

常见问题

Q:file_get_contents()fopen() 读取文件有什么区别? A:file_get_contents() 会将整个文件内容一次性读入内存,使用简单,适合读取小文件。fopen() 返回文件句柄,可以逐块或逐行读取,适合处理大文件,能有效控制内存占用。一般规则:文件小于 10MB 用 file_get_contents(),超过则用 fopen() 流式读取。

Q:写入文件时如何防止并发冲突导致数据丢失? A:使用 flock() 文件锁,或者在 file_put_contents() 中传入 LOCK_EX 标志:file_put_contents('file.txt', $data, LOCK_EX)。对于高并发场景,文件锁性能有限,建议改用数据库或 Redis 来存储需要并发写入的数据。

Q:如何安全地处理用户上传的文件? A:需要做多层验证:① 用 finfo 检查文件真实 MIME 类型,不信任 $_FILES['type'];② 限制允许的扩展名白名单;③ 限制文件大小;④ 用随机字符串重命名文件;⑤ 将上传目录设置为不可执行;⑥ 图片文件可以用 GD 库重新生成,彻底消除嵌入的恶意代码。

Q:PHP 读取远程文件需要注意什么? A:file_get_contents()` 支持读取 HTTP/HTTPS URL,但生产环境建议使用 cURL 或 Guzzle,原因是可以设置超时时间、处理重定向、错误处理更完善。另外,绝对不要将用户输入直接拼接到 URL 中读取,防止 SSRF(服务端请求伪造)攻击。