PHP文件夹存储慢的根源在文件系统层级,应采用哈希分层目录结构(如md5取前4字符拆为e/1/0/a)和opendir()+readdir()流式遍历,避免单目录文件过多及scandir()内存爆炸。

PHP 文件夹存储慢,通常不是 PHP 本身的问题
绝大多数情况下,mkdir()、scandir()、file_put_contents() 等操作变慢,根源在文件系统层级:单目录下文件/子目录超过几千个,ext4 或 NTFS 的目录索引性能会明显下降;NFS 或某些云存储挂载点也常有高延迟。PHP 层能做的,是避免把所有文件堆进一个目录,以及减少不必要的磁盘 I/O。
用哈希分层代替 flat 目录结构
比如用户上传头像,不要全存 uploads/avatar/123456.jpg,而应按 ID 哈希拆成多级子目录。这样单个目录内文件数可控,readdir() 和 stat() 开销大幅降低。
-
md5('123456')得到'e10adc3949ba59abbe56e057f20f883e',取前 4 字符'e10a'拆为e/1/0/a/ - 路径变为
uploads/avatar/e/1/0/a/123456.jpg,单目录最多 256 个子目录,每层几乎无压力 - 注意别用
sha1()或uniqid()—— 后者不唯一且无散列性,sha1()计算开销略高,md5()足够且更快
避免频繁调用 is_dir() 和 mkdir()
每次写入前检查目录是否存在再创建,看似稳妥,实则引入多次系统调用。尤其在并发场景下,is_dir() + mkdir() 存在竞态(两个请求同时判断不存在,然后都尝试创建,第二个会失败)。
- 改用
mkdir($path, 0755, true)的第三个参数true,它会自动递归创建缺失的父目录,失败时抛出 warning(可配合@抑制或用error_get_last()捕获) - 如果必须判断,优先用
file_exists($path),它比is_dir()少一次 stat 类型判断,在 Linux 下略快 - 对高频路径(如日志根目录),可在启动时预建好几级常用目录,避免运行时反复创建
慎用 scandir() 遍历海量文件夹
当某个目录下有上万文件时,scandir() 会一次性读取全部目录项到内存,极易触发 OOM 或超时。它不适合做“清理过期文件”或“统计文件数”这类任务。
立即学习“PHP免费学习笔记(深入)”;
- 改用
opendir()+readdir()流式读取,边读边处理,内存恒定 - 若需按时间筛选,优先依赖文件名中嵌入的时间戳(如
log_20240520_142305.txt),避免调用filemtime()—— 每次调用都是额外的stat()系统调用 - Linux 下可考虑用
shell_exec('find /path -name "*.tmp" -mmin +60 -delete 2>/dev/null')交由 find 处理,比 PHP 遍历快一个数量级(但需确认 shell 权限与安全性)
真正卡住的从来不是 PHP 函数怎么写,而是目录结构是否适配文件系统的物理特性。哈希分层和流式遍历这两条,覆盖了 90% 的实际瓶颈场景。剩下那 10%,往往得看是不是在 NAS 上跑 PHP,或者是不是开了 SELinux 限制了 stat 调用频率。











