先说个场景:你维护的 Windows 服务器磁盘又红了,打开资源管理器一层层点,这个目录几十 GB、那个目录十几个 GB,点到最后手指都酸了。这时候你会怀念 Linux 下那个叫 du 的命令——一条du -sh *直接按目录给你排好,谁大谁小一目了然。可惜 Windows 命令提示符里敲 du,大概率只会提示“du 不是内部或外部命令”。那 Windows 上到底能不能用上 du?或者说,有没有跟 du 一样顺手、一样能打的办法?这篇文章就把这事彻底讲清楚。
我不会只给你一个“替代品”的名字就完事,而是会把 Windows 上所有能实现 du 效果的路子都捋一遍:微软官方命令行工具、图形化扫描器、Git Bash 和 WSL 白嫖原生 du、以及自己写 PowerShell 脚本。你完全可以按自己的使用场景直接“抄作业”。
1. 先搞清楚:Windows 为什么没有原生的 du
1.1 du 到底解决了什么问题
Linux 用户对 du 的感情很深,原因不是它功能有多花哨,而是它把“磁盘占用统计”这件事做得很纯粹。你给它一个目录,它递归往下算,把每个子目录、每个文件占用的磁盘块数汇总出来,再以人类可读的大小展示。du -sh *这个组合拳,能让你在三秒内知道当前目录下哪个文件夹最肥、哪个文件该清理。
反观 Windows,磁盘满了以后你只能打开资源管理器,右键点属性看目录大小。这个操作有两个致命问题:一是没法批量看,一次只能看一层;二是没有排序,你根本不知道哪个子目录是罪魁祸首。哪怕是运维老手,遇到一个塞了几万个小文件的目录,用资源管理器一层层点属性也能点到怀疑人生。
所以 Windows 用户真正缺的不是“统计磁盘占用”的能力,缺的是一个称手的、能像 du 一样快速定位大目录的工具。du 这个名字虽然叫磁盘使用量,但它在实际工作中的价值更像一个“容量体检器”:帮你快速判断到底是谁把磁盘吃掉了。
1.2 直接拿 Windows 自带命令硬上的三个痛点
有人说 Windows 也不是完全没法统计,dir /s也能算出目录总大小。这话没错,但用起来相当难受,我总结下来有三个痛点:
第一,dir /s会把目录下所有文件列表刷屏式地打印出来,几百上千个文件糊满屏幕后,真正的汇总数字被淹没在末尾,你得翻好几页才能找到。第二,它只能给出一个目录的总大小,不能按子目录、按文件类型去拆解,无法回答“到底是哪个目录在膨胀”这个问题。第三,它没有任何排序和过滤能力,不能只列出最大的前十个目录,也不能排除掉某个路径,所有逻辑都得靠人眼从一长串输出里慢慢找。
至于 PowerShell,虽然Get-ChildItem -Recurse | Measure-Object -Sum能算出总大小,但它在遍历大量小文件时性能很差,一个几十 GB 的大目录常常要跑好几分钟。而且每次要写一长串管道命令,说实话比 Linux 的 du 差远了。这些都是我在实际环境里踩过的坑,所以后面会给出更靠谱的方案。
2. 实现方案横评:从命令行到图形工具的五种路子
2.1 最正统的标准答案:Sysinternals du.exe
如果你想要一个和 Linux du 最接近的、纯命令行的工具,首选是微软官方 Sysinternals 套件里的 du.exe。这个工具是微软自己出的,免费,不需要安装,下载解压就能用,也没有任何安全风险。
实际用法非常简单:
du.exe -accepteula -nobanner -q D:\data这条命令会统计D:\data目录的总大小并输出字节数。加-v参数可以递归列出每个子目录的大小,类似 Linux 的du -h --max-depth效果,非常适合快速定位哪个子目录占了大量空间。加-s可以只给出汇总总和。它的输出格式是纯文本,可以直接重定向到文件里做后续分析。
Sysinternals du 最大的优势是它对 Windows 路径、长路径、权限模型处理得比 Linux 的 du 更顺手,毕竟是原生 Windows 工具。它不会像资源管理器那样一遇到 Junction 链接就傻眼,也不用担心路径分隔符的转换问题。缺点是它是命令行工具,没有可视化界面,对不熟悉命令行的朋友不太友好。另外它的统计速度属于中等偏上,比 PowerShell 快很多,但比不上后面说的图形工具那么夸张。它最适合的场景是:服务器维护、脚本调用、需要把结果导出给其他程序处理的自动化场景。
2.2 图形工具的正确打开方式:WizTree 与 WinDirStat 怎么选
如果你主要是在自己电脑上交互式地排查“C 盘怎么又满了”,那我强烈建议直接用图形化工具,省时省力。这类工具里我首推 WizTree,它和 Linux 的 du 思路不同,但效果更猛。
WizTree 能快到一个什么程度呢?扫描一块 1TB 的 NTFS 硬盘通常只需要几秒钟,原因是它直接解析 NTFS 的主文件表(MFT),而不是像普通工具那样一个文件一个文件地遍历。MFT 相当于 NTFS 文件系统的“目录索引”,记录了每个文件的文件名、大小、位置,WizTree 直接读这个索引,速度自然快得离谱。WinDirStat 则是更老牌的开源工具,用色块来可视化每个文件占用的空间,视觉效果很好,但它是全盘遍历的,速度比 WizTree 慢得多,动辄需要几分钟甚至更久。
还有一个常被提到的 TreeSize Free,界面做得比较精致,能按目录树层层展开统计,免费版也够用。我个人选择逻辑是这样的:日常要快速定位大文件,用 WizTree;想直观地看哪些文件类型占空间最多,用 WinDirStat;需要把目录树大小导出成报告,再考虑 TreeSize Free。图形工具做交互排查很强,但在自动化脚本里用不上,这时候就得回到命令行工具。
其实图形工具最大的价值在于“第一轮快筛”。我遇到磁盘告警,第一件事就是开 WizTree 对整个盘扫一遍,几秒钟就能锁定最大的那个目录,然后再用命令行工具对这个目标目录做精确统计和清理,效率比纯命令行高太多了。
2.3 复用 Linux du:Git Bash 与 WSL 的白嫖方案
如果你电脑上已经装了 Git for Windows,或者开了 WSL(Windows Subsystem for Linux),那你其实根本不需要额外装别的统计工具,直接用 Linux 的原生 du 就行。
Git for Windows 自带了一套 MSYS2 环境,里面包含了 coreutils,所以C:\Program Files\Git\usr\bin\du.exe是真实存在的。打开 Git Bash 后,Windows 的盘符会被映射成 Linux 风格的路径:C:\变成/c/,D:\变成/d/。于是你可以直接这样用:
cd /d/data du -sh *这一下就和 Linux 上的体验完全一样了,还能用du -h --max-depth=1按目录深度展开,用du -h --max-depth=1 | sort -hr按大小排序。Git Bash 自带 sort、awk、grep 这些命令,组合起来能做很花哨的统计分析。
WSL 的情况更接近真实 Linux。你可以在 WSL 里直接访问 Windows 分区,路径挂载在/mnt/c、/mnt/d下面,然后运行:
wsl du -sh /mnt/d/data/*这个方案的好处是 du 的实现是纯正的 Linux coreutils,所有参数、管道、脚本习惯都能直接复用。缺点是跨文件系统访问有性能损耗,尤其是 WSL2 通过 9P 协议访问 Windows 分区时,扫描大量小文件会比较慢。不过对大文件、大目录的统计来说,性能还是能接受的,而且胜在顺手。
2.4 方案对比总表
我把上面这几种方案放在一起对比,方便你按自己的场景快速做选择:
| 方案 | 类型 | 速度 | 上手难度 | 适用场景 | 备注 |
|---|---|---|---|---|---|
| Sysinternals du.exe | 命令行 | 中等偏上 | 低 | 服务器、脚本自动化 | 微软官方,免安装 |
| WizTree | 图形界面 | 极快(秒级) | 极低 | 本地交互排查 | 直接读 NTFS MFT |
| WinDirStat | 图形界面 | 慢 | 低 | 可视化分析文件类型 | 开源免费 |
| TreeSize Free | 图形界面 | 中等 | 低 | 目录树报告导出 | 免费版够用 |
| Git Bash du | 命令行 | 中等 | 低 | 已装 Git 的用户 | 路径格式要适应 |
| WSL du | 命令行 | 中低(跨盘) | 中等 | 习惯 Linux 命令的用户 | 需要先装 WSL |
| PowerShell 脚本 | 命令行 | 较慢 | 中等 | 定制化自动化 | 灵活但性能有限 |
我的建议很直接:本地排障用 WizTree,服务器和脚本用 Sysinternals du,日常想在命令行里体验原汁原味的du -h就开 Git Bash 或 WSL。
3. 手写一个 PowerShell 版 du:脚本讲解与参数优化
3.1 基础版:统计目录总大小
如果你不想安装任何第三方工具,只想在 PowerShell 里快速看一个目录的总大小,下面的脚本就是最简版本:
$path = 'D:\data' $totalBytes = (Get-ChildItem -Path $path -Recurse -Force -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum '{0:N2} GB' -f ($totalBytes / 1GB)这条命令的核心逻辑很简单:Get-ChildItem递归取出目录下所有文件,-Force把隐藏文件和系统文件也算进去,-File只保留文件对象,-ErrorAction SilentlyContinue把无权访问的路径错误吞掉,然后Measure-Object -Property Length -Sum把所有文件的字节数加起来,最后除以 1GB 转成 GB 输出。
实际操作中你肯定会遇到一个坑:如果目录为空或者全部路径都无权访问,Measure-Object -Sum返回的结果是$null,此时直接做除法会报错。稳妥的写法是加一个空值保护:
$totalBytes = 0 Get-ChildItem -Path $path -Recurse -Force -File -ErrorAction SilentlyContinue | ForEach-Object { $totalBytes += $_.Length } '{0:N2} GB' -f ($totalBytes / 1GB)这个版本的性能会更差一点,因为一条一条累加比 Measure-Object 慢,但胜在稳定。如果你只是偶尔看一下目录大小,上面任意一种都够用,没必要追求极限性能。
3.2 增强版:一键列出 Top N 大目录
基础版只能看一个目录的总大小,实际排障时更常用的是“列出某目录下最大的 N 个子目录”。我把这个需求封装成一个函数,你可以直接复制到 PowerShell profile 里长期使用:
function Get-DirectorySize { param([string]$Path = '.') $bytes = (Get-ChildItem -Path $Path -Recurse -Force -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum [PSCustomObject]@{ Path = $Path SizeBytes = [int64]$bytes SizeGB = [math]::Round($bytes / 1GB, 2) } } Get-ChildItem 'D:\data' -Directory | ForEach-Object { Get-DirectorySize -Path $_.FullName } | Sort-Object SizeBytes -Descending这里有一个细节值得注意:排序时我按SizeBytes而不是SizeGB排。因为多个目录的 GB 值经过四舍五入后可能相等,按原始字节排序才能保证顺序精确。实际输出中,你还可以在Sort-Object后面接Select-Object -First 10 -Projection只取前十个结果。
脚本能跑通之后,我强烈建议把它封装成一个“目录大小排行榜”工具。我在实际运维中经常需要对比同一数据目录下面几十个子项目的磁盘占用,用这个函数跑一遍,按大小排序输出,直接就能知道哪些项目已经膨胀到需要扩容或者清理的程度,非常直观。你甚至可以把它写成:
Get-ChildItem 'D:\data' -Directory | ForEach-Object { Get-DirectorySize -Path $_.FullName } | Sort-Object SizeBytes -Descending | Format-Table -AutoSize这样输出的表格会非常清晰,每一行是一个子目录,大小从大到小排列。
3.3 按文件类型归组:找出谁在占空间
有时候磁盘空间不是被某个目录吃掉的,而是被某类文件吃掉的。比如服务器日志目录虽然分散在很多项目里,但罪魁祸首都是.log文件。遇到这种场景,按目录统计就失效了,你得按文件扩展名归组统计:
Get-ChildItem -Path 'D:\data' -Recurse -File -Force -ErrorAction SilentlyContinue | Group-Object Extension | Select-Object @{n='Extension';e={if ($_.Name) {$_.Name} else {'(无扩展名)'}}}, Count, @{n='SizeMB';e={[math]::Round((($_.Group | Measure-Object Length -Sum).Sum) / 1MB, 2)}} | Sort-Object @{e='SizeMB';Descending=$true} | Select-Object -First 15这段脚本会把目录下所有文件按扩展名分组,统计每组文件数量和总大小,最后按大小倒序排出前十五名。运行结果会告诉你到底是.mp4文件占空间,还是.bak备份文件堆积太久,还是.tmp临时文件在悄悄消耗磁盘。
这个功能在“磁盘持续增长”的场景下特别有用。有一次我接手一台文件服务器,磁盘每个月都会多出 20 GB,排查目录发现是分散在几百个用户目录下的邮件附件。后来用按扩展名归组的脚本一跑,发现大头是.ost离线邮箱文件,问题定位就非常快了。所以这个脚本值得好好保留。
3.4 自动化与性能优化
Write-Output 的 PowerShell 脚本最大的问题是性能,尤其是Get-ChildItem -Recurse在大目录树下会非常慢。我实测过一个 500 GB 左右、文件数大约 30 万的文件服务器共享目录,PowerShell 脚本要跑十几分钟,而 Sysinternals du 只需要两三分钟,WizTree 更是几秒完事。
如果你坚持要用 PowerShell 做自动化巡检,几个性能优化思路可以组合使用:
一是并行化。PowerShell 7 里可以用ForEach-Object -Parallel,把顶层目录拆给多个线程分别统计,再汇总结果。对于多核服务器,速度提升非常明显。
二是避开 PowerShell 的慢遍历。遇到特别大的目录树时,我会直接用robocopy的列表模式来快速获取总字节数。robocopy 是 Windows 自带的工具,对长路径、权限问题容错性极好,跑一次robocopy D:\data C:\__dummy__ /L /S /NJH /BYTES,日志末尾会输出文件总数和总字节数,速度比 PowerShell 遍历快不少。
三是混合方案。先跑一遍 WizTree 全盘扫描拿到大目录排序,再对前几个大目录用 PowerShell 脚本做精确到文件级的明细统计,兼顾速度与灵活性,这个组合我用了很久。
4. 从 Docker 和 WSL 的场景看磁盘统计的真实需求
4.1 Docker Desktop 的 vhdx 膨胀怎么定位
现在很多人在 Windows 上装了 Docker Desktop,磁盘被吃光后打开资源管理器半天找不到原因,其实很大程度上是被 Docker 的虚拟磁盘文件坑了。Docker Desktop 默认采用 WSL2 后端,所有镜像、容器、数据卷都存放在一个大的虚拟磁盘文件里,路径一般是:
C:\Users\<你的用户名>\AppData\Local\Docker\wsl\data\ext4.vhdx这个ext4.vhdx是 Docker 内部的 Linux 文件系统镜像,Windows 资源管理器根本看不到里面的内容,只知道这个文件可能高达几十 GB。想定位里面到底什么占空间,用 Windows 侧的 du 类工具没有意义,你必须进入 Docker 对应的 WSL 发行版或者用docker system df来看:
docker system df -v这条命令会列出镜像、容器、本地数据卷、构建缓存的详细占用,比盲目删 vhdx 安全得多。如果发现数据卷占空间很大,可以用下面的命令进入卷内看明细:
docker run --rm -v 卷名:/data alpine du -sh /data/*通过这个方式,容器内部的数据分布就很清楚了。平时做磁盘清理时,先跑docker system df判断缓存和悬空镜像的比例,再对症下药,不要一上来就删 vhdx,否则会把有用的镜像数据也删掉。
4.2 进容器里用原生 du 排查日志和数据卷
实际排查容器占用时,Linux 的 du 比 Windows 侧的工具都好用,因为容器本来就是 Linux 环境。我的常规操作是:
docker exec -it 容器名 sh -c "du -h --max-depth=1 / | sort -hr | head -20"这条命令直接进入容器,从根目录开始摸清占用分布。最常见的大户有三个:应用日志目录(比如/var/log)、包管理缓存(比如 apt 缓存)、以及数据库数据目录。日志目录特别容易失控,很多容器不配置日志轮转,一个晚上就能堆出好几个 GB。排查的时候重点看/var/lib/docker/containers下的 json 日志文件,这是 docker 默认的 stdout/stderr 存储位置,如果发现某容器日志已经巨大,就该配置 log rotate 了。
在 Windows 上装了 Docker Desktop 的读者,尤其要养成定期看容器磁盘占用的习惯。Windows 宿主机的 du 类工具看不到容器内部,而docker exec配合 du 是唯一顺手的路子。
4.3 WSL 发行版本体占用与瘦身
除了 Docker,WSL 发行版本身也会在 Windows 侧生成一个ext4.vhdx文件,位置形如:
C:\Users\<你的用户名>\AppData\Local\Packages\<发行版包名>\LocalState\ext4.vhdx这个文件也会随着使用不断膨胀。问题在于 WSL 里的删除操作通常不会让 vhdx 自动缩小,文件系统内部删了 20 GB,外面的 vhdx 可能仍然是原来的大小。想确认 WSL 发行版内部哪里占空间,直接在 WSL 里用 Linux du 就行了:
du -h --max-depth=1 / | sort -hr确认清理干净后,要让 vhdx 真正瘦身,需要一步步操作。先关闭 WSL:
wsl --shutdown然后通过 diskpart 压缩虚拟磁盘:
diskpart select vdisk file="C:\Users\<你的用户名>\AppData\Local\Docker\wsl\data\ext4.vhdx" attach vdisk readonly compact vdisk detach vdisk exit这里需要注意的是,compact 之前必须保证 WSL 已经关闭,否则磁盘被占用会报错。压缩虚拟磁盘的原理是把 vhdx 内部空闲块释放回宿主文件系统,所以只有 WSL 内部先删除文件,压缩才有明显效果。这一步做完,C 盘经常能多出十几个 GB 空间。
5. 常见问题与排查技巧实录
5.1 统计结果和资源管理器对不上,差在哪
很多人第一次用 du 类工具或脚本,第一反应都是“这个数字怎么和资源管理器属性里显示的不一样”。这里面的差异通常来自四个原因。
第一是隐藏文件和系统文件。资源管理器默认不显示隐藏文件,属性统计时不会把它们算进去,但du -force和 WizTree 这类工具会算。比如一个目录下藏着大量.git目录,资源管理器那个数字就会明显偏小。第二是回收站和卷影副本。回收站里的文件大小不计入源目录,但残留的卷影副本(VSS 快照)可能占着磁盘空间却在你统计的目录里看不到。第三是硬链接和符号链接。NTFS 硬链接会让同一份数据被多个目录引用,脚本按文件名累计时会重复计数。第四是稀疏文件和压缩文件。文件逻辑大小和实际占用磁盘的物理大小不同,很多统计工具默认显示逻辑大小,而资源管理器属性在某些版本里显示的是“大小”而不是“占用空间”。
所以我的习惯是:不纠结于到底哪个数字绝对准确,而是看趋势和粒度。你只需要知道哪些目录增长最快,哪个目录是主要矛盾,至于 5 GB 以内的偏差,大多来自上述系统机制,不影响判断。
5.2 一堆 Access Denied 要不要理会
用 PowerShell 跑全盘统计时,经常会刷出一堆“拒绝访问”的错误,集中在System Volume Information、C:\Windows\System32\config这类受系统保护目录。出现这些提示是正常的,哪怕是管理员账户,在没有 SYSTEM 权限的情况下也无法读取那些系统专用目录。
我的建议是:只要报错目录不属于你要分析的业务数据目录,直接用-ErrorAction SilentlyContinue忽略掉就行,不用特意去修改 NTFS 权限。强行拿takeown或者icacls改系统目录权限,很容易把系统搞出问题,得不偿失。如果你确实需要统计包含系统目录在内的全盘数据,那就用管理员权限运行工具,或者用 Sysinternals du 这类以管理员令牌运行的工具,但依然会有部分系统路径被跳过,这属于正常情况。
5.3 脚本闪退、乱码、执行策略被拦,怎么破
Windows 上跑 PowerShell 脚本最常见的问题有三个,我一个个排过,都有解。
第一个是“在此系统上禁止运行脚本”报错,这是 PowerShell 执行策略默认 Restricted 导致的。解决办法是在当前用户级别放行,执行:
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned或者运行时临时绕过限制:
powershell -ExecutionPolicy Bypass -File your-script.ps1第二个问题是中文路径乱码。这个通常不是脚本本身的问题,而是输出重定向时编码不对。PowerShell 5.1 默认输出到文件用的是系统 ANSI 编码,遇到 UTF-8 简体中文路径会变成乱码。解决办法是在脚本里把输出改成 UTF-8:[Console]::OutputEncoding = [System.Text.Encoding]::UTF8,或者Out-File -Encoding utf8。
第三个问题是脚本跑到一半直接闪退或者卡死,多见于大目录递归遍历。闪退多半是因为内存被大量对象撑爆,PowerShell 在处理几十万文件时会生成海量对象,极其吃内存。解决思路是分段统计、及时释放变量,或者干脆换 Sysinternals du / WizTree 来做大规模扫描。卡死则往往不是因为死循环,而是因为某个网络盘或 U 盘处于不健康状态,Get-ChildItem在等待 I/O 超时,这种情况可以加一个超时机制,或者把网络映射盘排除在统计范围之外。
5.4 长路径、符号链接、硬链接这些坑
Windows 的老版本限制了路径最大长度是 260 个字符,现在的 Windows 10/11 虽然支持长路径,但默认没有打开。如果你用 PowerShell 统计一个深层次的目录,路径超过了 MAX_PATH,脚本就会报DirectoryNotFoundException。解决办法有两个:一是在注册表里开启长路径支持,设置HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled为 1 并重启;二是用\\?\前缀给路径加上 Win32 文件命名空间的转义。Sysinternals du 对长路径的处理相对好一些,这也是我推荐它做服务器统计的原因之一。
符号链接和 Junction 链接则是另一个隐蔽陷阱。目录里如果有一个 Junction 指向了 C 盘,你在统计 D 盘时可能会把它当作普通目录递归进去,导致统计结果虚高,甚至出现递归死循环。PowerShell 里面判断链接目录的方法是检查Attributes是否包含ReparsePoint:
Get-ChildItem -Path $path -Directory | Where-Object { $_.Attributes -notmatch 'ReparsePoint' }WSL 和 Git Bash 里的 Linux du 默认不跟随符号链接,但 Windows 的 Junction 在某些挂载方式下会被当作普通目录遍历,这点也要留意。
5.5 问题速查表
| 症状 | 可能原因 | 处理方法 |
|---|---|---|
| “du 不是内部或外部命令” | Windows 没有原生 du | 装 Git/WSL/Sysinternals du 或改用 PowerShell 脚本 |
| 统计结果比资源管理器大 | 隐藏文件、硬链接被计入 | 用 -Force 确认明细,或参考物理占用而不是逻辑大小 |
| 统计结果比资源管理器小 | 权限拒绝、受保护目录被跳过 | 管理员权限运行工具,必要时接受遗漏 |
| 大量 Access Denied | 无 SYSTEM 权限 | 不用管;只能统计你有权限的部分 |
| 脚本运行报安全错误 | PowerShell 执行策略限制 | Set-ExecutionPolicy 或 Bypass |
| 脚本输出乱码 | 编码不匹配 | 强制设置 UTF-8 输出 |
| 遍历时卡死 | 网络盘 I/O 或目录树过大 | 排除网络盘、分段统计、换工具 |
| 路径找不到/超长 | 超过 260 字符路径限制 | 开 LongPathsEnabled 或用 \?\ 前缀 |
| 统计里出现几百 GB 的虚拟文件 | vhdx 是 Docker/WSL 的虚拟磁盘 | 用 docker system df 进内部排查,再考虑 compact |
我再分享一个自己的习惯组合:日常交互排障先开 WizTree,几秒钟扫完整个 C 盘;写巡检脚本和定时任务时用 PowerShell 封装的统计函数,把结果输出成 CSV 丢给图表;遇到 Docker 或 WSL 的 vhdx 膨胀,就先docker system df定位内部占用,再按照压缩步骤瘦身。这套流程用了好几年,基本覆盖了 Windows 磁盘统计的绝大多数需求。
最后一个小技巧:不管用哪种工具统计,请记得先确认你是不是管理员,很多“漏算”“报错”并不是工具的问题,而是权限不够。把权限提到管理员级别再跑,结果会准很多,排查思路也会清晰得多。