什么是在使用PHP中搜索文件内容并修改的最佳(最有效)方法?

5
我有一个文件,正在用PHP读取。我想查找一些以一些空格开头,然后是我要查找的关键词(例如,“project_name:”)的行,并更改该行的其他部分。
目前,我处理这个问题的方式是将整个文件读入字符串变量中,操作该字符串,然后将整个内容写回文件,完全替换整个文件(通过fopen( filepath, "wb" )fwrite()),但这种方法效率低下。有更好的方法吗?

1
“Best”是主观的。从关闭原因来看,“我们期望答案有事实、参考资料或专业知识的支持,但这个问题很可能会引发辩论、争论、投票或长时间的讨论。”您是否考虑通过选择特定的方法并解释它不符合您的需求来改进您的问题?这将使我们能够提供具体而非主观的答案。 - George Cummins
4
@GeorgeCummins你的评论不适用于这里。这是一个典型的编程问题。 - hek2mgl
1
@Baba,你确定你的尝试比我假设的这个更快吗?(https://dev59.com/kXLYa4cB1Zd3GeqPcNf5#16887051)请注意,简单的“rename()`”非常快。我会准备一些基准测试 :) 还要注意的是,在大多数应用场景中,不知道应该替换字符串的位置 - hek2mgl
@hek2mgl:无论它是否是“典型的编程问题”,都完全不相关。这不是这里讨论的主题。 - Lightness Races in Orbit
1
@Baba,我还是不明白这是一个重复的问题,以及你的答案如何适用于此。你有一个函数应该替换文本的位置作为参数。请注意,此处未知位置。这是搜索和替换,而不是注入。您能告诉我我的答案有哪些问题吗?我想进行基准测试(也许改进它),但我无法比较两个解决方案,因为它们并不相同。我知道你很聪明,也许我在这里漏掉了什么。 - hek2mgl
显示剩余4条评论
1个回答

3
更新:完成我的功能后,我有时间对其进行基准测试。我使用了一个1GB的大文件进行测试,但结果令人不满 :|
是的,内存峰值分配显着较小:
- 标准解决方案:1.86 GB - 自定义解决方案:653 KB(4096字节缓冲区大小)
但与以下解决方案相比,只有轻微的性能提升:
ini_set('memory_limit', -1);

file_put_contents(
    'test.txt',
    str_replace('the', 'teh', file_get_contents('test.txt'))
);

上面的脚本需要大约16秒,而自定义解决方案只需要大约13秒。

总结:自定义解决方案对于大文件来说稍微更快,并且消耗的内存要少得多(!!!)。

此外,如果您想在Web服务器环境中运行此操作,则自定义解决方案更好,因为许多并发脚本可能会消耗系统中所有可用的内存。


原始答案:

我能想到的唯一方法是按适合文件系统块大小的块读取文件,并将内容或修改后的内容写回临时文件。处理完成后,使用rename()覆盖原始文件。

这将减少内存峰值,并且如果文件确实很大,则应显着加快速度。

注意:在Linux系统上,您可以使用以下命令获取文件系统块大小:

sudo dumpe2fs /dev/yourdev | grep 'Block size'

我得到了4096

以下是函数:

function freplace($search, $replace, $filename, $buffersize = 4096) {

    $fd1 = fopen($filename, 'r');
    if(!is_resource($fd1)) {
        die('error opening file');
    }   

    // the tempfile can be anywhere but on the same partition as the original
    $tmpfile = tempnam('.', uniqid());
    $fd2 = fopen($tmpfile, 'w+');

    // we store len(search) -1 chars from the end of the buffer on each loop
    // this is the maximum chars of the search string that can be on the 
    // border between two buffers
    $tmp = ''; 
    while(!feof($fd1)) {
        $buffer = fread($fd1, $buffersize);
        // prepend the rest from last one
        $buffer = $tmp . $buffer;
        // replace
        $buffer = str_replace($search, $replace, $buffer);
        // store len(search) - 1 chars from the end of the buffer
        $tmp = substr($buffer, -1 * (strlen($search)) + 1); 
        // write processed buffer (minus rest)
        fwrite($fd2, $buffer, strlen($buffer) - strlen($tmp));
    };  

    if(!empty($tmp)) {
        fwrite($fd2, $tmp);
    }   

    fclose($fd1);   
    fclose($fd2);
    rename($tmpfile, $filename);
}

像这样调用它:

freplace('foo', 'bar', 'test.txt');

2
如果搜索字符串跨越了读取缓冲区,会发生什么? - Jon
是的,好处在于减少了内存峰值。您只需要使用 ~$buffersize 的内存,而不是文件大小的内存。但是,我会在有时间的时候更新帖子。也许在晚上。将准备一个处理Jon评论的版本.. 已经快完成了 :) - hek2mgl
使用多行正则表达式将会非常棘手。 - hek2mgl
@hek2mgl 谢谢。如果你能帮忙再找到两个“重新开放”的投票,那就更安全了。rename问题只是展示了微小的变化可能以未记录的方式产生巨大的影响。 - Don Rhummy
我已经花了一个 :) 重命名问题是基本的计算机操作。如果你有两个硬盘,那么在这些硬盘(或分区)之间传输数据是必要的。如果它们在一个分区上,只需要更改inode条目(设置新名称),而不改变数据,这非常快速且是原子操作。 - hek2mgl
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接