Directory.GetFileSystemEntries 会漏掉隐藏 / 系统文件,应改用 Directory.EnumerateFileSystemEntries 手动判断属性;FileInfo.Length 返回逻辑大小而非磁盘占用,需 Win32 API 获取真实字节数;递归遍历须用迭代避免栈溢出;符号链接、UNC 路径和系统目录需特殊处理。

Directory.GetFileSystemEntries 会漏掉隐藏 / 系统文件
直接用 Directory.GetFiles 默认不返回隐藏或系统属性的文件,导致计算结果偏小。尤其在 Windows 系统盘或用户目录下,pagefile.sys、hiberfil.sys 或 OneDrive 隐藏同步元数据都可能被跳过。
正确做法是显式传入 SearchOption.AllDirectories 和 SearchOption.TopDirectoryOnly 组合,并用 FileAttributes.AllAttributes 过滤时排除 FileAttributes.ExcludeArchive 类逻辑——但更稳妥的是绕过过滤,改用 Directory.EnumerateFileSystemEntries + 手动判断:
foreach (var entry in Directory.EnumerateFileSystemEntries(root, "*", SearchOption.AllDirectories)) {if ((File.GetAttributes(entry) & FileAttributes.ReparsePoint) == 0) // 跳过符号链接和挂载点 {if (File.Exists(entry)) {try { size += new FileInfo(entry).Length; } catch (UnauthorizedAccessException) {/* 忽略权限不足的文件 */} catch (IOException) {/* 忽略正在使用的文件(如 Outlook PST)*/} } } }
FileInfo.Length 返回的是逻辑大小,不是磁盘占用
一个 1KB 的文本文件在 NTFS 上实际可能占 4KB 簇空间;而稀疏文件或压缩文件(NTFS 压缩、WIM 挂载)的 FileInfo.Length 和磁盘实际写入字节数完全不同。
若需真实磁盘占用,必须调用 Win32 API:GetCompressedFileSize(兼容压缩 / 稀疏)或 GetDiskFreeSpaceEx 配合 FindFirstFile 获取 dwFileSizeHigh/low —— 但 C# 中最简方案是用 Microsoft.VisualBasic.FileIO.FileSystem.GetDriveInfo 的间接方式,或直接 P/Invoke:
[DllImport("kernel32.dll")] static extern uint GetCompressedFileSize(string lpFileName, out uint lpFileSizeHigh); // 注意:返回值为低 32 位,lpFileSizeHigh 输出高 32 位,需组合为 long
多数场景下用 Length 已足够(比如用户空间配额估算),但做备份校验或磁盘分析时,这个差异会导致 5%~30% 的误差。
递归遍历大目录时容易触发 StackOverflowException
纯递归(如自己写 CalculateSize(string path) 并对每个子目录调用自身)在深度 > 1000 层的路径结构中极易崩溃,且无法捕获。
应改用基于栈或队列的迭代遍历:
- 用
Stack<string></string>存待处理目录,每次Pop()后扫描其子项,把子目录Push()进栈 - 避免深递归,也便于加取消令牌(
CancellationToken)或进度回调 - 注意:Windows 路径最大长度为 260,启用长路径支持(
App.config加<runtime><enforceFIPolicy enabled="false"/></runtime>)后仍需用?前缀才能突破限制
符号链接、挂载点、网络驱动器需特殊处理
Directory.EnumerateDirectories 默认会跟随符号链接(ReparsePoint),造成重复计数甚至无限循环(如 A → B,B → A)。而网络驱动器(Z:)在断开时抛 DirectoryNotFoundException,但 servershare UNC 路径可能返回空枚举而不报错。
关键检查点:
- 用
File.GetAttributes(path) & FileAttributes.ReparsePoint判断是否为链接,再读FileSystemEnumerableIterator<T>底层或调用DeviceIoControl区分类型 - 对 UNC 路径,先用
Path.IsUncPath判断,再用NetworkInterface.GetIsNetworkAvailable()做粗略连通性预检 - 跳过
$RECYCLE.BIN、System Volume Information等系统保护目录——它们即使有权限也可能返回 0 字节或拒绝访问
真正难的不是算数,而是决定「哪些该算、哪些该跳、哪些算不准却得假装知道」——尤其是跨平台部署时,Linux/macOS 的硬链接、ACL、扩展属性会让同一套逻辑行为完全不同。