scandir() 比 glob() 快因直接调用系统 readdir(),而 glob() 需启动 shell 模式匹配并遍历所有文件比对,CPU 和系统调用开销大,数千文件时可慢 3–5 倍。

为什么 scandir() 比 glob() 快得多
PHP 默认用 scandir() 读取目录内容,它直接调用系统 readdir(),开销极小;而 glob() 会启动 shell 模式匹配引擎,哪怕只是 glob("*.php"),也会遍历所有文件再逐个比对后缀,CPU 和系统调用成本明显更高。尤其在含数千文件的目录中,glob() 可能慢 3–5 倍。
实操建议:
- 纯列举文件名(无通配需求):强制用
scandir($path),之后用array_filter()手动筛选,比glob()更可控 - 必须用通配时:优先用
DirectoryIterator+RegexIterator,避免重复构建正则或隐式类型转换 - 注意
scandir()默认包含"."和"..",记得用array_diff($files, [".", ".."])过滤
PHP 8.1+ 的 StreamWrapper 缓存目录列表是否靠谱
有人尝试用自定义 StreamWrapper 在 opendir() 后缓存 readdir() 结果,但实际效果有限——因为 PHP 不会自动复用同一路径的句柄,每次 opendir() 都是新调用;且缓存有效期难界定(文件可能被外部进程增删),容易返回脏数据。
更稳妥的做法是业务层主动缓存:
立即学习“PHP免费学习笔记(深入)”;
- 用
apcu_fetch()存md5_file($path . "/.listing_cache")或基于filemtime()的键,比如"dirlist_" . md5($path) . "_" . filemtime($path) - 避免缓存整个文件数组(内存膨胀),只缓存文件名列表,需要详细信息(如大小、时间)时再按需
stat() - 若目录极少变动(如静态资源目录),可生成 JSON 文件缓存,比 APCu 更持久
大量小文件场景下,RecursiveDirectoryIterator 的性能陷阱
RecursiveDirectoryIterator 看似方便,但它在每一层子目录都触发完整迭代器初始化和异常捕获逻辑,遇到权限不足或符号链接环时还会额外消耗。当目录深度 >3 或总文件数 >10k,它比手写递归 scandir() 慢 40% 以上。
替代方案:
- 用
exec("find " . escapeshellarg($path) . " -maxdepth 2 -type f -printf '%P\0')(Linux)配合explode("\0", $output),速度提升显著,但失去跨平台性 - 若必须用原生 PHP:改用
new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::LEAVES_ONLY),并设置FilesystemIterator::SKIP_DOTS减少无效项 - 禁用
RecursiveDirectoryIterator的FilesystemIterator::KEY_AS_PATHNAME(默认开启),改用KEY_AS_FILENAME,减少字符串拼接开销
Windows 下 opendir() 卡顿的常见原因
在 Windows 上,opendir() 偶尔卡住几秒,大概率不是 PHP 问题,而是 SMB 共享、OneDrive 同步或杀毒软件实时扫描导致。PHP 层面无法绕过这些系统级阻塞,但可以规避:
- 用
clearstatcache(true, $path)清除该路径的 stat 缓存,防止后续is_file()调用重复触发 NTFS 查询 - 避免在循环中反复调用
file_exists()或is_dir(),先用scandir()拿到全量名称,再批量stat()一次获取全部元信息 - 如果目录位于网络驱动器(如
Z:\),改用 UNC 路径(\\server\share\path)通常更稳定
opendir()、stat()、filemtime() 时,每个调用都可能触发独立的 NTFS 或 ext4 查询——把它们合并成单次 scandir() + 批量 stat(),收益远大于换函数。











