PHP: 文件操作
数据库存结构化数据,文件存非结构化数据——日志、配置文件、CSV 报表、用户上传的图片。这节课让你在 PHP 中自如操作文件和目录。
1. 读文件
PHP
<?php
// 方法一:file_get_contents() — 一行读取整个文件
$content = file_get_contents('hello.txt');
echo $content;
// 方法二:fopen + fread — 流式读取(大文件)
$handle = fopen('hello.txt', 'r');
$content = fread($handle, filesize('hello.txt'));
fclose($handle);
// 方法三:逐行读取(最省内存)
$handle = fopen('data.txt', 'r');
while (($line = fgets($handle)) !== false) {
echo trim($line) . "<br>";
}
fclose($handle);
// 方法四:file() — 一次性读入数组(每行一个元素)
$lines = file('data.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
print_r($lines);
?>
| 方法 | 适用场景 | 内存 |
|---|---|---|
file_get_contents() |
小文件(配置、JSON) | 一次性 |
fopen + fread |
中等文件 | 受控 |
fopen + fgets |
大文件(日志、CSV) | 省 |
file() |
按行处理的小文件 | 一次性 |
2. 写文件
PHP
<?php
// file_put_contents() — 一行写文件
file_put_contents('notes.txt', "今天学了 PHP 文件操作\n");
// 追加模式(第三个参数 FILE_APPEND)
file_put_contents('notes.txt', "现在会追加内容了\n", FILE_APPEND);
// 独占锁写入(防止并发冲突)
file_put_contents('counter.txt', $count, LOCK_EX);
// fwrite — 精细化控制
$handle = fopen('log.txt', 'a'); // 'a' = 追加模式
fwrite($handle, date("Y-m-d H:i:s") . " — 用户登录\n");
fclose($handle);
?>
▶ 示例:简易日志系统
PHP
<?php
class Logger {
public function __construct(
private string $logFile
) {}
public function info(string $message): void {
$this->write('INFO', $message);
}
public function error(string $message): void {
$this->write('ERROR', $message);
}
public function warning(string $message): void {
$this->write('WARNING', $message);
}
private function write(string $level, string $message): void {
$time = date("Y-m-d H:i:s");
$line = "[{$time}] [{$level}] {$message}\n";
file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
}
public function getRecent(int $lines = 20): array {
if (!file_exists($this->logFile)) {
return [];
}
$all = file($this->logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
return array_slice($all, -$lines);
}
}
$log = new Logger('app.log');
$log->info('程序启动');
$log->warning('配置文件未找到,使用默认值');
$log->error('数据库连接超时');
$log->info('程序结束');
echo "<h3>最近日志</h3><pre>" . implode("\n", $log->getRecent(10)) . "</pre>";
?>
3. 文件检测
PHP
<?php
$file = 'test.txt';
// 存在检查
var_dump(file_exists($file)); // bool
var_dump(is_file($file)); // 且是文件(不是目录)
var_dump(is_dir($file)); // 检查是否是目录
// 读写检查
var_dump(is_readable($file)); // 能否读取
var_dump(is_writable($file)); // 能否写入
// 文件信息
echo filesize($file) . " 字节<br>"; // 大小
echo filemtime($file); // 最后修改时间(Unix时间戳)
echo date("Y-m-d H:i:s", filemtime($file)) . "<br>"; // 转日期
echo pathinfo($file, PATHINFO_EXTENSION); // txt
?>
▶ 示例:文件缓存
PHP
<?php
class FileCache {
private string $cacheDir;
public function __construct(string $dir = 'cache') {
$this->cacheDir = $dir;
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0755, true);
}
}
public function get(string $key, int $ttl = 3600): mixed {
$file = "{$this->cacheDir}/{$key}.cache";
if (!file_exists($file)) return null;
if (time() - filemtime($file) > $ttl) {
unlink($file);
return null;
}
return unserialize(file_get_contents($file));
}
public function set(string $key, mixed $value): void {
$file = "{$this->cacheDir}/{$key}.cache";
file_put_contents($file, serialize($value), LOCK_EX);
}
}
$cache = new FileCache();
$cache->set('config', ['app_name' => 'MyBlog', 'version' => '1.0']);
print_r($cache->get('config'));
?>
4. 文件操作函数速查
PHP
<?php
$src = 'source.txt';
$dst = 'backup.txt';
// 复制
copy($src, $dst);
// 重命名/移动
rename($src, 'new-name.txt');
rename('/tmp/upload.txt', '/var/www/files/upload.txt'); // 移动
// 删除
unlink('old-file.txt');
// 目录操作
mkdir('new-folder', 0755, true); // true = 递归创建
rmdir('empty-folder'); // 只能删空目录
// 遍历目录
foreach (scandir('uploads') as $item) {
if ($item !== '.' && $item !== '..') {
echo "{$item} — " . (is_dir("uploads/{$item}") ? '目录' : '文件') . "<br>";
}
}
// 用 glob 按模式查找文件
$images = glob('uploads/*.{jpg,png,gif}', GLOB_BRACE);
echo "找到 " . count($images) . " 张图片";
?>
5. CSV 读写
CSV 是数据交换的通用格式——Excel 能打开,任何语言都能读写:
PHP
<?php
// === 写 CSV ===
$data = [
['姓名', '年龄', '城市'],
['小明', 25, '北京'],
['小红', 22, '上海'],
['小刚', 28, '广州'],
];
$handle = fopen('users.csv', 'w');
// 写 UTF-8 BOM(让 Excel 正确识别中文)
fwrite($handle, "\xEF\xBB\xBF");
foreach ($data as $row) {
fputcsv($handle, $row);
}
fclose($handle);
echo "CSV 文件已生成 ✅<br>";
// === 读 CSV ===
$handle = fopen('users.csv', 'r');
// 跳过 BOM
fseek($handle, 3);
// 读表头
$headers = fgetcsv($handle);
while (($row = fgetcsv($handle)) !== false) {
// 用表头当键
$item = array_combine($headers, $row);
echo "{$item['姓名']},{$item['年龄']}岁,在{$item['城市']}<br>";
}
fclose($handle);
?>
▶ 示例:CSV 数据导出
PHP
<?php
function exportToCSV(array $data, string $filename): void {
header('Content-Type: text/csv; charset=utf-8');
header("Content-Disposition: attachment; filename={$filename}");
$output = fopen('php://output', 'w');
fwrite($output, "\xEF\xBB\xBF"); // BOM
// 表头
if (!empty($data)) {
fputcsv($output, array_keys($data[0]));
}
// 数据
foreach ($data as $row) {
fputcsv($output, $row);
}
fclose($output);
}
// exportToCSV($usersData, 'users_export.csv');
?>
6. 文件锁定(防并发写)
PHP
<?php
class Counter {
private string $file;
public function __construct(string $file = 'counter.dat') {
$this->file = $file;
if (!file_exists($this->file)) {
file_put_contents($this->file, 0);
}
}
public function increment(): int {
$handle = fopen($this->file, 'c+');
// 获取排他锁(其他进程等待)
if (flock($handle, LOCK_EX)) {
$count = (int)fread($handle, 100);
$count++;
// 回到文件开头
fseek($handle, 0);
ftruncate($handle, 0); // 清空
fwrite($handle, (string)$count);
flock($handle, LOCK_UN); // 解锁
fclose($handle);
return $count;
}
fclose($handle);
return -1;
}
}
?>
❓ 常见问题
Q
file_get_contents() 和 fopen + fgets 怎么选?A 小文件(<1MB)用
file_get_contents(),一行搞定。大文件(日志数万行)用 fgets() 逐行读,内存友好。未知大小先用 fopen。Q 中文在 Excel 中乱码?
A CSV 文件开头加 UTF-8 BOM(
"\xEF\xBB\xBF")解决。Excel 默认按本地编码读取,BOM 告诉它这是 UTF-8。Q 文件锁定
flock 是真的必须的吗?A 多人同时读写同一文件时必须有。计数器、缓存、日志等场景如果不加锁,可能读到的值已被其他进程修改(竞态条件)。
📖 小节
file_get_contents()一行读文件,file_put_contents()一行写文件fopen + fgets逐行读,适合大文件file_put_contents($file, $data, FILE_APPEND | LOCK_EX)安全追加file_exists()/is_readable()/is_writable()先检查再操作- CSV:
fputcsv()写,fgetcsv()读,BOM 防 Excel 乱码 flock($handle, LOCK_EX)排他锁防并发写冲突
📝 作业
- 写一个访客计数器:每次刷新页面,数字 +1,用文件存储计数。
- 写一个简易留言板:表单提交留言→追加写入文件→从文件读取展示所有留言(附时间戳)。
- 把用户在数据库中导出为 CSV 文件(从 MySQL 读数据 → 生成 CSV 文件 → 提供下载链接)。