在php中读取并覆盖文件内容的最佳方法是什么?


What's the best way to read from and then overwrite file contents in php?

在php中打开文件,读取内容,然后使用基于原始内容的输出覆盖文件内容的最干净的方法是什么?具体来说,我试图打开一个用项目列表填充的文件(以换行符分隔),处理/添加项目到列表中,从列表中删除最老的N个条目,最后将列表写回文件。

fopen(<path>, 'a+')
flock(<handle>, LOCK_EX)
fread(<handle>, filesize(<path>))
// process contents and remove old entries
fwrite(<handle>, <contents>)
flock(<handle>, LOCK_UN)
fclose(<handle>)

注意,我需要用flock()锁定文件,以便在多个页面请求中保护它。当fopen()执行时,"w+"标志会起作用吗?php手册声明它将截断文件到零长度,所以看起来可能会阻止我读取文件的当前内容。

如果文件不是太大(也就是说,您可以确信加载它不会超出PHP的内存限制),那么最简单的方法是将整个文件读入字符串(file_get_contents()),处理字符串,并将结果写回文件(file_put_contents())。这种方法有两个问题:

  • 如果文件太大(比如,几十或几百兆字节),或者处理需要内存,你将会耗尽内存(当你有多个实例运行时更是如此)。
  • 操作是破坏性的;当保存中途失败时,您将丢失所有原始数据。

如果存在上述任何一种问题,则B计划是处理文件并同时写入临时文件;成功完成后,关闭两个文件,重命名(或删除)原始文件,然后将临时文件重命名为原始文件名。

阅读

$data = file_get_contents($filename);

file_put_contents($filename, $data);

一个解决方案是使用一个单独的锁文件来控制访问。

此解决方案假设只有您的脚本,或您有权访问的脚本,想要写入该文件。这是因为脚本需要知道检查一个单独的文件是否可以访问。

$file_lock = obtain_file_lock();
if ($file_lock) {
    $old_information = file_get_contents('/path/to/main/file');
    $new_information = update_information_somehow($old_information);
    file_put_contents('/path/to/main/file', $new_information);
    release_file_lock($file_lock);
}
function obtain_file_lock() {
    $attempts = 10;
    // There are probably better ways of dealing with waiting for a file
    // lock but this shows the principle of dealing with the original 
    // question.
    for ($ii = 0; $ii < $attempts; $ii++) {
         $lock_file = fopen('/path/to/lock/file', 'r'); //only need read access
         if (flock($lock_file, LOCK_EX)) {
             return $lock_file;
         } else {
             //give time for other process to release lock
             usleep(100000); //0.1 seconds
         }
    }
    //This is only reached if all attempts fail.
    //Error code here for dealing with that eventuality.
}
function release_file_lock($lock_file) {
    flock($lock_file, LOCK_UN);
    fclose($lock_file);
}

这将防止并发运行的脚本读取旧信息并更新旧信息,从而导致您丢失在读取文件后另一个脚本更新的信息。它将只允许脚本的一个实例读取文件,然后用更新的信息覆盖它。

虽然这有望回答最初的问题,但它并没有给出一个好的解决方案,以确保所有并发脚本最终都能够记录它们的信息。