scandir()配合is_file()最稳,需跳过.和..、检查is_readable()、限制递归深度、windows下用unc路径并检查scandir()返回值。

递归统计目录文件数时 scandir() 和 is_file() 配合最稳
直接用 scandir() 读取目录内容,再逐项判断是否为文件,比 glob() 或 DirectoryIterator 更可控,尤其在权限受限或符号链接多的环境里不容易崩。注意跳过 . 和 .. 这两个特殊条目,否则会无限递归或重复计数。
实操建议:
- 用
array_diff(scandir($dir), ['.', '..'])干净过滤掉父目录和当前目录引用 - 对每个子项先调用
is_file()判断——别用filetype(),它在某些 NFS 或容器挂载路径下可能返回 false 或警告 - 遇到
is_dir()为 true 的项才递归,且务必检查is_readable(),避免因权限不足导致scandir()报 Warning 并中断执行
RecursiveIteratorIterator 套 RecursiveDirectoryIterator 适合简单统计但不报错
这套组合能自动跳过不可读目录,不会抛出警告,适合“尽力而为”的场景(比如日志分析脚本),但代价是无法区分失败原因——你不知道某个子目录是被跳过了还是根本不存在。
实操建议:
- 初始化
RecursiveDirectoryIterator时传FilesystemIterator::SKIP_DOTS,省去手动过滤./.. - 用
getChildren()或迭代器遍历时,current()->isFile()比is_file((string)$item)更可靠,尤其在路径含中文或特殊字符时 - 别依赖
count()直接统计迭代器——它会把目录本身也计入,必须显式遍历 +isFile()判断
递归深度过大时容易触发 PHP 内存溢出或超时
PHP 默认栈深度和内存限制对深层嵌套目录很敏感,比如 20 层以上的嵌套或单目录含数万文件时,scandir() 一次性加载全部文件名就会吃光内存。
实操建议:
- 用
set_time_limit(0)解除脚本超时,但更关键的是加深度限制:在递归函数参数里传入$depth,到达阈值(如 16)就 return 0 并记录警告 - 改用
opendir()+readdir()流式读取,边读边判别边计数,内存占用恒定在 KB 级 - 如果只是要总数,考虑用系统命令兜底:
exec("find '$dir' -type f | wc -l", $out);,但必须确保$dir经escapeshellarg()处理,否则有命令注入风险
Windows 下路径分隔符和长路径需额外处理
Windows 默认禁用长路径(>260 字符),而 PHP 的 scandir() 在遇到超长路径时会静默返回 false,不报错也不提示,极易漏统计。
实操建议:
- 在 Windows 上调用前,把路径转成 UNC 格式:
\\?\C:\path\to\dir,可绕过长度限制 - 统一用
DIRECTORY_SEPARATOR拼接路径,避免硬写/或导致is_dir()返回 false - 对
scandir()返回结果做空值检查:if ($items === false) { error_log("Failed to scan: $dir"); return 0; },别假设它一定成功
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











