文件读写
文件读写是PHP中最基本也是最重要的操作之一。通过文件读写,我们可以存储数据、读取配置、处理日志等。本节将详细介绍PHP中的各种文件读写方法。
学习目标
完成本节学习后,你将能够:
- 掌握不同的文件读取方法
- 学会安全地写入文件内容
- 理解文件指针和定位操作
- 处理大文件的读写技巧
- 了解文件锁定的使用方法
文件读取的基本方法
1. file_get_contents() - 最简单的读取方法
file_get_contents() 是最简单易用的文件读取函数,适合读取小文件。
<?php
// 基本用法
$content = file_get_contents('example.txt');
if ($content === false) {
echo "读取文件失败";
} else {
echo "文件内容:" . htmlspecialchars($content);
}
// 读取远程文件(如果PHP配置允许)
$remoteContent = file_get_contents('https://www.example.com');
if ($remoteContent !== false) {
echo "远程文件长度:" . strlen($remoteContent) . " 字节";
}
?>
2. file() - 按行读取文件
file() 函数将文件的每一行作为数组元素返回。
<?php
// 读取文件到数组
$lines = file('example.txt');
if ($lines === false) {
echo "读取失败";
} else {
echo "文件共有 " . count($lines) . " 行<br>";
// 显示前5行
for ($i = 0; $i < min(5, count($lines)); $i++) {
$lineNumber = $i + 1;
echo "第{$lineNumber}行:" . htmlspecialchars($lines[$i]) . "<br>";
}
}
// 使用参数控制读取行为
$lines = file('example.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
// FILE_IGNORE_NEW_LINES:忽略每行的换行符
// FILE_SKIP_EMPTY_LINES:跳过空行
// FILE_TEXT:以文本模式读取(Windows下转换换行符)
// FILE_BINARY:以二进制模式读取
echo "处理后的行数:" . count($lines) . "<br>";
?>
3. fopen/fread/fclose - 流式读取
对于大文件,使用流式读取更节省内存。
<?php
// 基本的流式读取
$handle = fopen('large_file.txt', 'r');
if ($handle === false) {
echo "无法打开文件";
} else {
// 读取整个文件(适合大文件)
$content = '';
while (!feof($handle)) {
$chunk = fread($handle, 8192); // 每次读取8KB
if ($chunk !== false) {
$content .= $chunk;
}
}
fclose($handle);
echo "读取完成,总长度:" . strlen($content) . " 字节";
}
// 按行读取
$handle = fopen('data.csv', 'r');
if ($handle !== false) {
$lineCount = 0;
while (($line = fgets($handle)) !== false) {
$lineCount++;
echo "第{$lineCount}行:" . htmlspecialchars(trim($line)) . "<br>";
// 只显示前10行
if ($lineCount >= 10) {
break;
}
}
fclose($handle);
}
?>
文件写入的基本方法
1. file_put_contents() - 最简单的写入方法
<?php
// 基本写入
$data = "Hello, PHP文件写入!\n这是第二行。";
$result = file_put_contents('output.txt', $data);
if ($result === false) {
echo "写入失败";
} else {
echo "成功写入 {$result} 字节";
}
// 追加写入
$additionalData = "\n这是追加的内容。";
file_put_contents('output.txt', $additionalData, FILE_APPEND);
// 使用锁定防止并发写入
$lockedData = "需要安全写入的内容";
$result = file_put_contents('important.txt', $lockedData, LOCK_EX);
// FILE_APPEND: 追加模式
// LOCK_EX: 独占锁定
// FILE_TEXT: 文本模式
// FILE_BINARY: 二进制模式
?>
2. fwrite/fopen - 流式写入
<?php
// 写入文件
$handle = fopen('output.txt', 'w');
if ($handle !== false) {
fwrite($handle, "第一行内容\n");
fwrite($handle, "第二行内容\n");
fwrite($handle, "第三行内容\n");
fclose($handle);
echo "文件写入完成";
}
// 追加写入
$handle = fopen('output.txt', 'a');
if ($handle !== false) {
fwrite($handle, "这是追加的内容\n");
fclose($handle);
}
// 文件模式说明:
// 'r' - 只读,从文件开头开始
// 'r+' - 读写,从文件开头开始
// 'w' - 只写,清空文件内容(如果不存在则创建)
// 'w+' - 读写,清空文件内容
// 'a' - 只写,从文件末尾开始追加(如果不存在则创建)
// 'a+' - 读写,从文件末尾开始追加
// 'x' - 只写,创建新文件(如果文件已存在则失败)
// 'x+' - 读写,创建新文件
?>
高级文件操作
1. 文件指针定位
<?php
// 文件指针操作示例
$handle = fopen('data.txt', 'r+');
if ($handle !== false) {
// 写入初始内容
fwrite($handle, "这是第一行\n这是第二行\n这是第三行\n");
// 回到文件开头
rewind($handle);
echo "当前位置:" . ftell($handle) . "<br>"; // 输出: 0
// 读取第一行
$firstLine = fgets($handle);
echo "第一行:" . htmlspecialchars($firstLine) . "<br>";
echo "当前位置:" . ftell($handle) . "<br>";
// 移动到指定位置
fseek($handle, 10); // 移动到第10个字节
echo "移动后位置:" . ftell($handle) . "<br>";
// 从当前位置读取
$remaining = fread($handle, 100);
echo "剩余内容:" . htmlspecialchars($remaining) . "<br>";
// 移动到文件末尾
fseek($handle, 0, SEEK_END);
echo "文件末尾位置:" . ftell($handle) . "<br>";
// 在文件末尾添加内容
fwrite($handle, "追加到末尾的内容\n");
fclose($handle);
}
?>
2. CSV文件处理
<?php
// 写入CSV文件
$data = [
['姓名', '年龄', '邮箱'],
['张三', 25, 'zhangsan@example.com'],
['李四', 30, 'lisi@example.com'],
['王五', 28, 'wangwu@example.com']
];
$handle = fopen('users.csv', 'w');
if ($handle !== false) {
foreach ($data as $row) {
fputcsv($handle, $row);
}
fclose($handle);
echo "CSV文件写入完成";
}
// 读取CSV文件
$handle = fopen('users.csv', 'r');
if ($handle !== false) {
echo "<h3>用户列表:</h3>";
echo "<table border='1'>";
echo "<tr><th>姓名</th><th>年龄</th><th>邮箱</th></tr>";
$isFirstRow = true;
while (($row = fgetcsv($handle)) !== false) {
if ($isFirstRow) {
$isFirstRow = false;
continue; // 跳过标题行
}
echo "<tr>";
echo "<td>" . htmlspecialchars($row[0]) . "</td>";
echo "<td>" . htmlspecialchars($row[1]) . "</td>";
echo "<td>" . htmlspecialchars($row[2]) . "</td>";
echo "</tr>";
}
echo "</table>";
fclose($handle);
}
?>
3. 配置文件读写
<?php
// 写入INI配置文件
$config = [
'database' => [
'host' => 'localhost',
'username' => 'root',
'password' => 'password',
'database' => 'myapp'
],
'app' => [
'name' => 'My Application',
'version' => '1.0.0',
'debug' => true
]
];
// 写入PHP数组格式
$content = "<?php\nreturn " . var_export($config, true) . ";\n";
file_put_contents('config.php', $content);
// 写入JSON格式
$jsonContent = json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
file_put_contents('config.json', $jsonContent);
// 读取配置文件
$phpConfig = include 'config.php';
$jsonConfig = json_decode(file_get_contents('config.json'), true);
echo "<h3>PHP配置:</h3>";
echo "数据库主机:" . $phpConfig['database']['host'] . "<br>";
echo "<h3>JSON配置:</h3>";
echo "应用名称:" . $jsonConfig['app']['name'] . "<br>";
?>
实用示例
1. 简单的计数器
<?php
// 网站访问计数器
class Counter {
private $file;
public function __construct($file = 'counter.txt') {
$this->file = $file;
// 初始化计数器文件
if (!file_exists($this->file)) {
file_put_contents($this->file, '0');
}
}
public function increment() {
$current = (int)file_get_contents($this->file);
$current++;
// 使用文件锁定
file_put_contents($this->file, $current, LOCK_EX);
return $current;
}
public function getCount() {
return (int)file_get_contents($this->file);
}
public function reset() {
file_put_contents($this->file, '0', LOCK_EX);
}
}
// 使用计数器
$counter = new Counter();
$count = $counter->increment();
echo "您是第 {$count} 位访问者!<br>";
echo "总访问次数:" . $counter->getCount();
?>
2. 简单的日志记录器
<?php
// 日志记录器类
class Logger {
private $logFile;
public function __construct($file = 'app.log') {
$this->logFile = $file;
}
public function log($message, $level = 'INFO') {
$timestamp = date('Y-m-d H:i:s');
$logEntry = "[{$timestamp}] [{$level}] {$message}" . PHP_EOL;
// 追加到日志文件
file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
}
public function error($message) {
$this->log($message, 'ERROR');
}
public function warning($message) {
$this->log($message, 'WARNING');
}
public function info($message) {
$this->log($message, 'INFO');
}
public function getRecentLogs($lines = 10) {
if (!file_exists($this->logFile)) {
return [];
}
$content = file_get_contents($this->logFile);
$allLines = explode(PHP_EOL, trim($content));
// 返回最后几行
return array_slice($allLines, -$lines);
}
}
// 使用日志记录器
$logger = new Logger();
$logger->info('应用程序启动');
$logger->warning('内存使用率较高');
$logger->error('数据库连接失败');
echo "日志记录完成<br>";
// 显示最近5条日志
$recentLogs = $logger->getRecentLogs(5);
echo "<h3>最近日志:</h3>";
foreach ($recentLogs as $log) {
echo htmlspecialchars($log) . "<br>";
}
?>
3. 文件备份工具
<?php
// 简单的文件备份类
class FileBackup {
private $backupDir;
public function __construct($backupDir = 'backup') {
$this->backupDir = $backupDir;
if (!is_dir($this->backupDir)) {
mkdir($this->backupDir, 0755, true);
}
}
public function backupFile($sourceFile) {
if (!file_exists($sourceFile)) {
throw new Exception("源文件不存在:{$sourceFile}");
}
$basename = basename($sourceFile);
$timestamp = date('YmdHis');
$backupFile = $this->backupDir . "/{$basename}.{$timestamp}.backup";
if (copy($sourceFile, $backupFile)) {
return $backupFile;
} else {
throw new Exception("备份失败:{$sourceFile}");
}
}
public function restoreBackup($backupFile, $targetFile) {
if (!file_exists($backupFile)) {
throw new Exception("备份文件不存在:{$backupFile}");
}
// 备份当前文件(如果存在)
if (file_exists($targetFile)) {
$this->backupFile($targetFile);
}
if (copy($backupFile, $targetFile)) {
return true;
} else {
throw new Exception("恢复失败:{$backupFile}");
}
}
public function listBackups($pattern = null) {
$backups = [];
$files = scandir($this->backupDir);
foreach ($files as $file) {
if ($file === '.' || $file === '..') {
continue;
}
if ($pattern !== null && strpos($file, $pattern) === false) {
continue;
}
if (preg_match('/^(.+)\.(\d+)\.backup$/', $file, $matches)) {
$originalFile = $matches[1];
$timestamp = $matches[2];
$backupPath = $this->backupDir . '/' . $file;
$backups[] = [
'original' => $originalFile,
'timestamp' => $timestamp,
'date' => date('Y-m-d H:i:s', strtotime($timestamp)),
'path' => $backupPath,
'size' => filesize($backupPath)
];
}
}
return $backups;
}
}
// 使用备份工具
try {
$backup = new FileBackup();
// 创建测试文件
file_put_contents('test.txt', '这是测试文件内容');
// 备份文件
$backupFile = $backup->backupFile('test.txt');
echo "文件已备份到:" . htmlspecialchars($backupFile) . "<br>";
// 修改原文件
file_put_contents('test.txt', '修改后的内容');
// 再次备份
$backup->backupFile('test.txt');
// 列出所有备份
$backups = $backup->listBackups('test.txt');
echo "<h3>备份列表:</h3>";
foreach ($backups as $b) {
echo "文件:{$b['original']},日期:{$b['date']},大小:{$b['size']} 字节<br>";
}
} catch (Exception $e) {
echo "错误:" . $e->getMessage();
}
?>
最佳实践
1. 错误处理
<?php
// 安全的文件读取函数
function safeFileRead($filename) {
if (!file_exists($filename)) {
throw new Exception("文件不存在:{$filename}");
}
if (!is_readable($filename)) {
throw new Exception("文件不可读:{$filename}");
}
$content = file_get_contents($filename);
if ($content === false) {
throw new Exception("读取文件失败:{$filename}");
}
return $content;
}
// 安全的文件写入函数
function safeFileWrite($filename, $content) {
// 确保目录存在
$directory = dirname($filename);
if (!is_dir($directory)) {
if (!mkdir($directory, 0755, true)) {
throw new Exception("无法创建目录:{$directory}");
}
}
// 使用锁定写入
$result = file_put_contents($filename, $content, LOCK_EX);
if ($result === false) {
throw new Exception("写入文件失败:{$filename}");
}
return $result;
}
// 使用示例
try {
$content = safeFileRead('input.txt');
$modifiedContent = strtoupper($content);
safeFileWrite('output.txt', $modifiedContent);
echo "文件处理完成";
} catch (Exception $e) {
echo "错误:" . $e->getMessage();
}
?>
2. 内存管理
<?php
// 处理大文件的逐行处理函数
function processLargeFile($filename, $callback) {
$handle = fopen($filename, 'r');
if ($handle === false) {
throw new Exception("无法打开文件:{$filename}");
}
try {
$lineNumber = 0;
while (($line = fgets($handle)) !== false) {
$lineNumber++;
$callback($line, $lineNumber);
}
} finally {
fclose($handle);
}
}
// 使用示例:统计文件行数和字符数
$lineCount = 0;
$totalChars = 0;
processLargeFile('large_file.txt', function($line, $lineNum) use (&$lineCount, &$totalChars) {
$lineCount++;
$totalChars += strlen($line);
// 每处理1000行输出一次进度
if ($lineNum % 1000 === 0) {
echo "已处理 {$lineNum} 行<br>";
}
});
echo "处理完成:<br>";
echo "总行数:{$lineCount}<br>";
echo "总字符数:{$totalChars}<br>";
?>
总结
文件读写是PHP开发中的基础技能,掌握好这些技巧对实际开发非常重要。
关键要点:
-
选择合适的方法:
file_get_contents/file_put_contents:适合小文件和简单操作fopen/fread/fwrite:适合大文件和精细控制file/fgetcsv/fputcsv:适合按行处理和CSV文件
-
安全考虑:
- 始终检查文件是否存在和可读写
- 使用文件锁定防止并发问题
- 处理错误和异常情况
-
性能优化:
- 对大文件使用流式处理
- 及时关闭文件句柄
- 避免一次性读取过大的文件
-
实用技巧:
- 使用文件锁定确保数据一致性
- 创建备份文件保护重要数据
- 使用适当的文件模式
通过本节的学习,你应该能够熟练处理PHP中的各种文件读写操作。