DiskScope:找出是谁在悄悄吃掉你的硬盘
一个 Windows 磁盘分析 + 清理工具。扫 16 万个文件只要 1.7 秒, 并且会告诉你谁在长, 而不只是谁大。
起因
C 盘又满了。
每次都得开个现成的工具, 然后等它慢慢扫, 看进度条慢慢转, 扫完再自己对着几百兆的 node_modules 猜哪些能删。
这些工具做的是快照: 告诉你此刻谁占地方。但我真正想知道的是另一件事 ——
这个目录是什么时候开始变大的? 是不是某个东西在偷偷长?
Steam 的 workshop 素材会自动重新下载并换目录 ID, 某些项目隔三差五 npm install 一次, 缓存目录悄悄膨胀到几十 GB —— 这些在静态快照里完全看不出来。
所以我写了 DiskScope,全程vibecoding将想法变成现实。



四个页面
1. 体积分布
Squarified treemap, 点任意方块下钻, 配可展开的层级明细。目标是一眼看出空间去哪了。
2. 时间轴(核心)
最近 30 天每天写入了多少数据, 堆叠柱状图, 外加一张「谁在悄悄变大」排行榜。
这是这个工具和别的磁盘分析器最不一样的地方:
▼text复制代码今天写入 578 MB │ 最近 7 天 1.8 GB │ 最近 30 天 5.4 GB │ 30 天以上没动过 142 GB
一眼就能分清: 上面那 5.4 GB 是活的东西, 下面那 142 GB 是死的。后者才是清理的目标。
3. 清理
53 条规则, 分三级:
| 级别 | 数量 | 含义 | 例子 |
|---|---|---|---|
| 🟢 可重建 | 39 | 删了会自动重新生成, 默认勾选 | node_modules target dist __pycache__ |
| 🟡 需确认 | 12 | 要重新下载, 或会丢历史记录, 默认不勾 | .venv Pods *.log .idea |
| 🔴 勿删 | 2 | 勾选框直接禁用 | .git *.tfstate |
配套 dry-run 预演: 先看清要动什么, 再决定删不删。
4. 大文件
面向普通文件夹(照片 / 视频 / 安装包 / 归档)。构建垃圾规则匹配不到这些, 所以单独追踪: Top 200 大文件 + 按扩展名汇总。
技术上花了力气的地方
16 个 worker 线程, 单线程的 5.5 倍
第一版用 fs.promises.stat + 异步池, 16 万文件要 8.4 秒。瓶颈不是 Promise 开销, 是 stat 系统调用本身。
改成 worker 线程池后, 主进程只管派活, worker 各扫各的:
| worker 数 | 文件/秒 | 相对单线程 |
|---|---|---|
| 1 | 20,844 | 1.0x |
| 4 | 63,343 | 3.0x |
| 8 | 90,923 | 4.4x |
| 16 | 114,088 | 5.5x |
实际效果: 扫完 161,207 个文件 / 70 GB, 1.7 秒。
31 格直方图, 任意层级 O(1) 回答「最近长了多少」
每个目录存一个 31 格的直方图(第 0 格 = 今天, 最后一格 = 30 天前的冷数据), 自底向上滚动相加:
▼js复制代码// 父目录的直方图 = 自己的 + 所有子目录的 for (let i = 0; i < NDAYS; i++) daily[i] += child.daily[i];
于是任意层级都能 O(1) 回答"这里最近 7 天长了多少", 不需要重新遍历子树。
有界小顶堆, 内存恒定
要记住最大的 200 个文件, 但不能随扫描量涨内存。堆顶始终是 Top-N 里最小的那个, 新文件比它还小就直接丢弃:
▼js复制代码push(v) { if (a.length < cap) { a.push(v); this._up(a.length - 1); } else if (v[0] > a[0][0]) { a[0] = v; this._down(0); } // 堆顶换掉 }
扫 5 万个文件和扫 500 万个文件, 内存都是 200 条。
踩过的坑(这部分可能比功能更有用)
1. Electron 的 asar 补丁会悄悄改掉你的统计结果
Electron 会把「路径最后一个扩展名是 asar 的文件」当成 asar 包来解析:
- 合法的 asar →
statSync返回 0 字节(把包根当虚拟目录) - 损坏的 asar → 直接抛
Invalid package
结果就是所有 .asar 文件的大小都没被统计, 磁盘总量一直偏小, 而且 stderr 刷一片错误。
绕法是加 NTFS 备用数据流后缀 ::$DATA(指向文件本体), 路径最后一个扩展名变成 DATA, Electron 就不会去解析 asar 了:
▼js复制代码function statFile(p) { if (/\.asar$/i.test(p)) { try { return fs.statSync(p + "::$DATA"); } catch { /* 退回普通方式 */ } } return fs.statSync(p); }
2. D:\ 自带尾随分隔符, 拼路径会多一个反斜杠
▼js复制代码path.resolve("D:\\") // "D:\\" ← 带尾随分隔符 dirPath + "\\" + fn // "D:\\software" ← 双反斜杠 path.join(dirPath, fn) // "D:\software" ✓
Windows 内部会容忍双斜杠, 但路径字符串比较会失效 —— 我那个「文件被挪走了, 帮你找回来」的功能就是这么失灵的。改用 path.join 一劳永逸。
3. 悬停提示会残留在页面上
提示框原本是每次 hover 新建一个 div 挂在 document.body 上, 引用存在柱子元素里。点击柱子下钻 → 图表整个重建 → 旧元素被丢弃 → mouseleave 永远不触发 → 提示框永久留在屏幕上。
修法是改成全局单个复用的节点, 从根上消除这类孤儿节点。
4. 文件会「被挪到隔壁去」
Steam 重新下载 workshop 素材时会换一个新的目录 ID, 文件名却不变:
▼text复制代码扫描时: ...\431960\344551134\CSCT-013.mp4 现在: ...\431960\3445511134\CSCT-013.mp4 ← 多了一个 1
于是点路径时文件「不存在了」。现在的处理是分三档:
- 目标在 → 正常定位
- 被挪到隔壁去了 → 按文件名在上一级的其他子目录里找一遍, 找到就直接定位到新位置
- 真没了 → 打开最近还存在的上级目录, 并明确告诉你
三种情况都会给提示, 不会静默无反应。
安全设计
清理工具删的是用户的数据, 所以默认必须是保守的:
- 默认进回收站(可还原), 而不是直接
rm - dry-run 预演先过一遍
- 三级分类,
.git永远不可勾选 - 服务端硬性闸门: 磁盘根目录、工具自身所在目录和程序文件, 一律拒绝
- 嵌套路径自动去重(选了
node_modules就不会再单独处理它内部的.vite) - Electron 版把 Python 版那套「本地 HTTP + 随机 token」整个去掉了 —— 改成 IPC, 连端口都不占
测试
118 项检查, 打包版也跑:
▼text复制代码规则校验 27 项 │ 后端 40 项 │ 界面 51 项
界面自检是真的去点按钮的 —— 包括点「停止」并验证扫描真的停下来了, 点路径并验证资源管理器真的打开了。
获取
绿色版(免安装, 单文件):
▼text复制代码dist\DiskScope-1.0.0-绿色版.exe
从源码构建:
▼bash复制代码npm install npm run build # electron-builder + 写入图标与版本信息 npm run uitest # 界面自检, 会自动截图
Python 版(只用标准库, 双击 run.bat 就能跑)也保留着, 两版共用同一套前端和同一份规则表。
源码: https://github.com/star-sda/diskscope
最后
工具的开发全程vibecoding,作为一名javaer真切感受到了AI的强大。

