同事把 项目\2026\暖通 整个目录 Shift+Delete 了。或者把移动硬盘当 U 盘,手一滑点了格式化。或者插上 U 盘弹出”需要格式化才能使用”。
这种时候,人的第一反应是去下载一个数据恢复软件。然后你会看到这样的流程:扫描两小时,列出十几万个文件,勾选,导出一小时,打开一看——一半是坏的。更糟的情况是扫完显示 0 个文件,于是你换第二个软件、第三个软件,继续扫,继续等。
拾遗(ShiYi) 是我写的一个 Windows 数据恢复工具,只做恢复这一件事。它和上面那个流程最大的区别是:在你花时间之前,先把坏消息告诉你。
- 哪些文件恢复出来大概率是好的,哪些多半已经被覆盖了——扫描结果里直接标出来,恢复之前就知道。
- 如果这块盘根本扫不出东西(比如 SSD 已经 TRIM 过),开扫之前就告诉你,不让你干等两小时。
单文件 exe,无需安装,可以从 U 盘直接运行。纯 Python 标准库 + ctypes,没有任何第三方依赖。
一、它能做什么,不能做什么
先说清楚边界。数据恢复这个领域,夸大能力的软件太多了。
| 场景 | 用哪种扫描 | 能不能恢复 |
|---|---|---|
| Shift+Delete 删除、清空回收站 | 快速扫描 | 能,成功率高 |
| 误删整个项目目录(含子目录结构) | 快速扫描 | 能,目录结构一并重建 |
| 相机 SD 卡 / U 盘误删(exFAT) | 快速扫描 | 能,exFAT 保留得最完整 |
| FAT32 / FAT16 / FAT12 上误删 | 快速扫描 | 能,但有固有缺陷(见第四节) |
| 快速格式化(NTFS / exFAT / FAT) | 完整扫描 | 能 |
| 分区被删、分区表损坏、盘变 RAW | 完整扫描 | 能 |
| 完全格式化、全盘写零 | —— | 不能,数据已被物理覆盖 |
| SSD 删除后经过 TRIM | —— | 不能,主控已把块擦除,读回全零 |
| BitLocker 加密卷(未解锁) | —— | 不能,读到的是密文 |
“覆盖后的深度恢复”是不存在的。 任何声称能做到的软件都在骗人。数据被新数据盖掉之后,物理上就没有了。
出事之后第一件事:立刻停止使用那块盘。 不要往里存东西、不要装软件、不要”整理一下再恢复”。删除只是把文件标记成可覆盖,内容还在原地;一旦有新数据写进去,被覆盖的那部分谁也救不回来。如果丢数据的是系统盘,最稳妥的做法是关机,把盘挂到另一台机器上读。
二、只读是一条硬红线
数据恢复工具最大的风险不是”恢复不出来”,而是把还能恢复的数据毁掉。
拾遗从头到尾不写盘:不分区、不格式化、不修引导、不”修复”文件系统。这条红线有三道防线:
- 架构上——接触设备句柄的只有一个模块(
core/diskio.py),句柄一律只用GENERIC_READ打开。就算上层代码写错了,内核也会拒绝改盘。 - 测试上——自检里有一条源码扫描断言:全项目源码中不得出现任何写盘 API。每次构建都会跑,不过就不让打包。
- 交互上——恢复的输出目录必须在另一块物理磁盘上。程序会顺着”输出路径 → 卷 → 物理磁盘号”查一遍,和源盘是同一块就直接拒绝,并说清楚为什么:往源盘写数据会覆盖掉那些还没恢复出来的文件,这是数据恢复里最常见、也最无法挽回的操作。
顺带一提,连预览也全程在内存里做,绝不落临时文件——万一那个临时文件恰好写回了源盘呢。
三、三个和别的恢复软件不一样的地方
1. 恢复之前就告诉你哪些是好的
扫描结果里每个文件都有可恢复性评级。判据不是猜的:拿这个文件占用的簇,去和当前卷的分配表(NTFS 的 $Bitmap、exFAT 的分配位图、FAT 的 FAT 表)交叉验证。
| 评级 | 含义 |
|---|---|
| 优 | 元数据完整,占用的簇仍然是空闲的 |
| 良 | 有碎片、少量簇已被重新分配,或者簇位置是”假设连续”推出来的 |
| 差 | 簇已被明显覆盖 / 元数据缺段 / 长度靠推测 |
| 不可恢复 | 没有簇位置信息,或者是 EFS 加密内容 |
一个”已删除”文件占用的簇,如果在位图里已经被标成”已分配”,说明它多半已经被新文件盖掉了——恢复出来必然是坏的。这种文件会被标红,而不是让你导出一小时之后自己发现。
评级为”优”的文件还会再抽查首、中、尾三个簇:如果全是零,多半是 TRIM 过的固态硬盘,数据已被主控擦除。这时评级直接降级并给出提示。这个抽样成本极低,但能在恢复 500GB 之前就告诉你”这盘上的东西已经被擦了”。
2. 扫不出东西的时候,告诉你为什么
选中设备就先做体检,在开扫之前说清楚:
- 目标是 SSD 且 TRIM 已启用 → 删除的数据大概率已被主控擦除,扫描可能一无所获,这不是软件故障。
- 这是 BitLocker 加密卷 → 解锁前读到的都是密文,任何恢复软件都无能为力。
- 没有管理员权限 → 现在就提示并提供”以管理员身份重启”按钮,而不是扫到一半报 Access Denied。
3. 捞出来的东西必须能自证
完整扫描要在全盘的原始扇区里”捞”元数据,这件事最容易出的问题是误报——把一堆随机字节解析成”文件”,让用户对着一屏垃圾发愁。所以每一条结果都要过一道自校验才会被列出来:
| 捞的是什么 | 凭什么信它 |
|---|---|
| NTFS 的旧 MFT 记录 | fixup 校验必须过(相当于一个逐扇区的校验和) |
| exFAT 目录项集 | 把删除时清掉的 InUse 位补回去后,集校验和必须对得上 |
| FAT 目录簇 | 簇的开头必须是标准的 . / .. 两条目录项 |
过不了的一律丢掉。宁可少捞,也不给用户一堆解析出来的垃圾。自检里专门有一条用例:在纯随机数据里必须一个文件都捞不出来。
四、各文件系统的实话
不同文件系统的删除方式不一样,能救回多少东西差别很大。拾遗的做法是如实告知,而不是假装都一样好。
NTFS——最理想。删除只是把 MFT 记录的”在用”标志清零、把位图上的簇标回空闲,文件内容一字未动,文件名、时间、簇位置全都还在。小文件(约 700 字节以内)的内容直接存在 MFT 记录里,恢复出来 100% 完整。
exFAT——SD 卡和大容量 U 盘现在默认就是它,恢复效果很好。删除只把目录项类型字节的最高位清零(0x85→0x05),文件名一个字符都不丢,首簇和大小完好;而且绝大多数 exFAT 文件是连续存放的(NoFatChain 标志),根本不依赖 FAT 表。
FAT32 / FAT16 / FAT12——有两个改不了的缺陷:
- 删除时目录项首字节被改成
0xE5,文件名的第一个字符永久丢失。带长文件名(LFN)的文件还能从 LFN 项里救回完整名字;没有的只能显示成_EPORT.TXT,让你自己认。 - 删除时FAT 表里那条簇链被整条清零。链没了,就只能假设文件当初是连续存放的。文件连续时完美恢复;文件有碎片时,恢复出来的是几个文件的碎片拼盘——大小对、能打开、内容是花的。
第二条特别隐蔽,所以这类文件一律打上”假设连续”标记,评级封顶为”良”,并在说明里写清楚。这比恢复完让用户自己发现要诚实得多。
孤立文件:尽量把目录结构还原成原来的样子
删除的文件往往连父目录一起删了,甚至父目录的记录已经被别的文件复用。早期版本遇到这种情况直接把整条祖先链丢掉,恢复出来是一堆挂在 [孤立文件] 底下的散文件——你拿到手也不知道它原本属于哪个项目。
现在的做法是:父目录记录被复用时,那个目录名多半还是原来那个,所以继续往上追,把整条祖先链留下来,只是整体挂到 [孤立文件] 下面。你会看到
[孤立文件]\项目\2026\暖通\平面图.dwg
这样的完整层级,而不是散落一地。只有父记录彻底找不到时,才退化成 [孤立文件]\#记录号(同一个丢失目录下的文件仍然聚在一起)。
恢复时可以勾选「去掉 [孤立文件] 这一层」,直接按还原出的目录结构存放。默认不去掉——因为这几层是尽力还原的、不保证一定对,先如实告诉你,要不要当成原样由你决定。
五、两种扫描模式,都能中断续扫
快速扫描——遍历现有文件系统的元数据(NTFS 的 $MFT、exFAT/FAT 的目录树),挑出被删除但记录还在的文件。文件系统完好时用它,1TB 盘通常一分钟内扫完(实测约 2.6 万条 MFT 记录/秒,瓶颈在磁盘不在 CPU)。
完整扫描——不信任现有的分区表和引导扇区,把整块盘顺序读一遍,同时干四件事:找引导扇区(含 NTFS 卷尾的备份引导扇区,能反推出卷的起点)、捞旧 MFT 记录、捞 exFAT 目录项集、捞 FAT 目录簇。适用于快速格式化、分区丢失、卷变 RAW。
为什么快速格式化之后还能恢复?因为格式化只写了新的引导扇区和一个很小的新 $MFT,旧 MFT 的记录仍然原封不动躺在盘上。全盘搜出来,再用卷的簇大小和起点把簇号换算回实际位置就行。
中断了不用重来。 扫描结果边扫边落盘(.rscan 文件,gzip + JSON Lines),每隔一批就写一个检查点。中途暂停、中止、甚至进程被强杀,下次对同一块盘再点开始,就从断点那一条记录(完整扫描则是那个字节偏移)接着扫。命令行版按 Ctrl+C 也一样,再跑一遍同样的命令即可。
六、几个有意思的坑
写解析器最麻烦的地方在于:算错的时候通常不会崩溃,只会静默给出错误结果。 记几个印象深的:
NTFS 的 Fixup。 NTFS 把每个扇区的最后 2 字节替换成了一个序列号,真实值存在记录头的数组里。不还原就直接解析,不会崩,只会在每 512 字节边界上读到两个错误字节——表现为”偶尔解析出乱码文件名和荒谬的文件大小”,极难排查。
runlist 的偏移是有符号数,而且相对上一个片段。写成绝对值时,前几个文件恰好是对的,第五个开始全错。这种 bug 靠”随便试试”是发现不了的。
exFAT 的备份引导区。 exFAT 在第 12 个扇区放了一整份引导区副本。全盘搜索时它会被当成”第二个卷”——于是那个卷的簇号整体偏移 6144 字节,导出来的文件大小完全正确、内容却是别处的数据,肉眼根本看不出来。是自检当场抓出来的。
一块盘上有两个卷的时候。 认定卷长度时如果图省事用”从卷起点到盘尾”,第一个卷就会把后面的卷整个吞掉,后面卷里的文件会被算到前一个卷的簇基准上——同样是大小对、内容错。所以自检里专门有一个”一盘两卷”的用例守着它:两个卷的文件都要逐字节比对通过。
性能。 完整扫描原本要对每个数据块做 9 遍签名遍历(每种文件系统的引导扇区各一遍、MFT 一遍、exFAT 两遍、FAT 一遍),实测 154 MB/s——在 SSD 上反倒成了瓶颈。改成”先搜扇区尾的 55 AA,命中的扇区再认文件系统”,加上”盘上没有 exFAT 卷就不搜 exFAT 目录项”,遍数从 9 降到 2,提到 630 MB/s,瓶颈回到磁盘本身。
七、测试:全部跑在程序化生成的镜像上
真实磁盘不可控、不可重现,而且拿真盘做测试本身就有风险。所以拾遗的自检全部跑在代码生成的磁盘镜像上:不需要真磁盘、不需要管理员权限、可重现。
镜像生成器会造出结构真实的 NTFS(含 4Kn 扇区、MBR、GPT 变体)、exFAT、FAT12/16/32,删除动作严格按各文件系统的真实规则做——NTFS 清在用标志位、exFAT 清 InUse 位、FAT 改 0xE5 并清簇链。还包括”快速格式化后””分区表被清零””一盘两卷”这些场景。
目前 223 项自检,其中大约一半是”拒绝用例”:
- 越界的 runlist 必须报损坏,而不是将错就错
- fixup 校验不过的记录必须跳过
- GPT 头 CRC 校验不过必须拒收
- 结果文件和当前设备对不上必须拒绝
- 靠”假设连续”取出来的文件绝不能评为”优”
- 纯随机数据里必须一个文件都捞不出来
守住这类不变量的用例,比正例更重要。 上面提到的两个坑(exFAT 备份引导区、两卷吞并)都是被这些用例当场抓出来的。
每个恢复用例都做逐字节比对(blake2b),不是”能导出来就算过”。
八、怎么用
界面版四步:选设备 → 扫描 → 看结果 → 恢复。
结果页可以按名称/大小/类型/修改时间/可恢复性/来源排序,按扩展名分类和评级筛选,勾选后预览(图片、文本、DWG 内嵌缩略图),还能看到每个文件的簇分布(VCN → LCN)。恢复时可选保留原目录结构、全部平铺或按类型分文件夹,完成后生成一份 恢复报告.csv(UTF-8 BOM,Excel 打开中文不乱码)。
命令行版可以脚本化,带退出码:
shiyi-cli.exe list
shiyi-cli.exe info \\.\PhysicalDrive1 --find-volumes
shiyi-cli.exe scan D: --out D:\scan.rscan
shiyi-cli.exe scan \\.\PhysicalDrive1 --mode full --out E:\scan.rscan
shiyi-cli.exe show E:\scan.rscan --filter *.dwg --min-grade 良
shiyi-cli.exe recover E:\scan.rscan --to E:\recovered --filter *.dwg
退出码:0 成功 / 1 部分失败 / 2 参数或环境错误 / 3 没有可恢复的东西。
从源码跑也很简单,只要 Python 3.8+(界面需要 tkinter),不用装任何第三方包:
python selftest.py # 自检,不需要真磁盘、不需要管理员权限
python main.py # 开界面
python cli.py list # 命令行
build.bat # 自检通过才打包,打好的 exe 再自检一次
九、现在做到哪了
| 阶段 | 内容 | 状态 |
|---|---|---|
| M0 | 设备访问、镜像适配、测试镜像生成器 | 已完成 |
| M1 | MBR/GPT + NTFS 快速扫描 + 评级 + 恢复 + 界面 + CLI + 断点续扫 | 已完成 |
| M2 | exFAT、FAT32/16/12 | 已完成 |
| M3 | 完整扫描(快速格式化、分区丢失、RAW)+ 多卷分派 | 已完成 |
| M4 | 文件签名雕复 | 计划中 |
| M5 | 磁盘只读镜像、坏道重试策略 | 计划中 |
还没做的也说清楚:按文件签名雕复(M4)、坏道盘的只读镜像(M5)、RAID 重组、加密卷解密、物理故障盘的开盘修复——这些暂时都不在范围内。
十、最后
写这个工具的过程中最大的体会是:在数据恢复这件事上,诚实比功能更重要。
用户在最慌的时候找到你的软件。这时候多给一句”这个文件的簇已经被覆盖 100%,恢复出来大概率是坏的”,比多扫出一千个打不开的文件有价值得多。同样,扫不出东西的时候直接说”这块盘是 SSD、TRIM 开着,数据已经被主控擦了”,比让人换三个软件继续试要负责任。
工具只读、拒绝写回源盘、评级如实、限制写在明处——这几条加起来,才算是能放心交到别人手上的东西。
使用声明:本工具仅限对自有或已获明确授权的存储设备使用。用它读取他人设备上的数据可能违法。
(发布备注,贴到 WordPress 前可删除:本文适合归类到「工具 / 开发」,建议标签:数据恢复、NTFS、exFAT、FAT32、Python、Windows、开源工具。文中表格为标准 Markdown 表格,若站点未启用 Markdown 表格支持,可在块编辑器中改用表格区块。)