浅谈PHP中打开文件(fopen)的一些坑

准备自己实现一个基于文件的简单缓存类,用于一些小外包项目。原本只是打算按照特定的JSON格式进行存储,然后用很方便的两个函数 file_get_contentfile_put_content 进行读写
后来想给缓存类加个自增/减的方法,用来做简单的统计之类。如果考虑到瞬间并发情况,为了防止两个请求互相干扰,就必须得上文件锁

首先是设置缓存,这个没啥难度:

  • 首先一个$h = fopen($path, 'w+b')打开文件
  • 然后flock($h, LOCK_EX)锁定它,防止两次并发请求同时读写
  • 接着fwrite($h, 'xxx...')写入内容
  • 继续flock($h, LOCK_UN)解锁文件
  • 最后fclose($h)关闭文件指针

然后是读取缓存,按照上面流程如法炮制。只是把第3步的 fwrite 换成 fread,把读出来的内容解析一下返回。看起来好像没什么问题。

然而我无论怎么试,缓存总是能正常写入,却无法读取

这我就懵逼了。经过一阵调试,发现 fread 总是返回空,这是为毛!?
再一阵调试过后,我发现在 fopen 之后,文件就已经变成空了。这就很奇怪了
我打开官方文档,仔细地再次阅读了w+模式的说明

读写方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。

沃日感情你所谓的将文件大小截为零就是会清空文件内容的意思啊?这表达得也太隐晦了吧?
看来w+是不能用了。我试着把fopen的模式切换到 r+x+a+ 以及 c+,都无法达到先读取文件内容,然后覆盖写入的效果

中间折腾过程略……

后来发现一个函数 ftruncate ,文档中描述为将文件截断到给定的长度。我用 r+b 打开、锁定文件,读取内容后用该函数将其清空;递增/减数值后写入,解锁。看起来一切都那么顺利

然而我最终保存的文件,在正常内容前有一串奇怪的空字符,导致再次读取时格式不对
怀疑是清空内容后没有将指针复位。找到一个 rewind 函数,执行后再进行写入操作。最终一切正常,可喜可贺、可喜可贺!

18 条评论

昵称
  1. 胡杨

    程序员棒棒的

  2. 呆头空

    所以菊苣你的Pixiv挂件是不是得更新了 _(:з」∠)_考虑一下上HTTPS呗

    1. mokeyjay

      Pixiv挂件没啥问题呀,干嘛要更新,还是说你有什么想要的功能?至于HTTPS,我穷,免费的国内CDN不支持HTTPS,所以我也没法部署

      1. 呆头空

        VeryCloud可以,每个月免费50G流量

        1. mokeyjay

          还不够我用几天的

  3. 做整容手术要多少钱

    不错 谢谢 博主www.1688616.com

  4. 上海seo

    博主是程序员的啊?

    1. mokeyjay

      是啊,看不出来吗?

      1. 上海seo

        看出来了,哈哈

  5. 月的固执

    file_put_content 第三个参数 不是有锁定选项吗?会有什么问题吗?

    1. mokeyjay

      举个例子,有个缓存的值为0,通过一个接口我们可以递增这个缓存值。此时请求1开始file_get_content,然后值+1,正准备file_put_content时,另外一个并发请求2也开始file_get_content了,此时请求2获取到的值为0。也就导致请求2最终把缓存值设为1了。所以要做递增的话,必须保证从读取到写入期间一直独占锁定缓存文件