c# 没有内置自动分层存储 api,因 windows 不暴露访问频率统计接口,.net 也不监听底层 i/o;需自行埋点采样决策,且存在精度低、性能损耗大、误判风险高等实际限制。

为什么 C# 没有内置的「自动分层存储」API
因为 Windows 本身不暴露文件访问频率的实时统计接口,.NET 运行时更不会替你监听 ReadFile、CreateFile 这类底层 I/O 调用。所谓“根据访问频率自动移动”,得你自己埋点、采样、决策——不是调一个函数就能开箱即用的事。
常见错误现象:System.IO.File.Move 直接跨盘符搬文件,结果发现 SSD 上的热数据被手动挪到 HDD 后,程序卡顿;或者用 FileSystemWatcher 监听 Changed 事件,误把反病毒软件扫出的临时读取当成了用户高频访问。
- Windows 的
FILE_ACCESS_INFORMATION只在驱动层可用,用户态程序拿不到精确访问计数 -
GetFileInformationByHandleEx的FileIoPriorityHintInfo是写提示,不是读历史 - NTFS 的「最后访问时间」(
LastAccessTime)默认被系统禁用(为性能),且精度只有 1 小时,不可靠
怎么低成本采集真实访问频率
绕不开 hook,但不用写驱动。用 FileSystemWatcher + 用户态日志 + 时间窗口滑动统计,是折中方案。
使用场景:中小规模目录(比如 C:\Data\Projects),每天活跃文件数
- 监听
Changed和Renamed事件,忽略Created(避免初始化扫描干扰) - 每个文件路径映射一个
ConcurrentDictionary<string int></string>计数器,每 5 分钟清零并存入本地 SQLite 表(表结构:path TEXT, hour INTEGER, count INTEGER) - 避免监听整个盘符——会触发海量事件,改用递归注册子目录,且跳过
obj、bin、.git等噪声目录 - 注意:.NET 6+ 的
FileSystemWatcher在 WSL2 或网络路径下行为异常,只建议用于本地 NTFS 卷
移动文件时 SSD/HDD 判定和安全边界
不能只看盘符,得确认物理介质类型。Windows 从 Win10 1809 起提供 MSFT_PhysicalDisk WMI 类,C# 可查 MediaType 字段(3 = SSD,4 = HDD)。
参数差异:DriveInfo.IsReady 只判断盘是否挂载,DriveInfo.TotalFreeSpace 不能反映磨损或队列深度。
- 移动前必须校验目标盘剩余空间 ≥ 源文件大小 × 1.2(留出 NTFS 元数据和压缩余量)
- 用
File.Copy+File.Delete替代File.Move跨卷操作,否则可能因权限或硬链接失败静默丢数据 - SSD 上禁止频繁小文件( 20 个文件(磁头寻道瓶颈)
- 若目标盘是 BitLocker 加密卷,需提前用
ManageBde.exe -status确认解密状态,否则CopyFileAPI 可能卡死
什么时候该放弃自动迁移,改用策略性预热
真正容易被忽略的点:多数业务场景里,“访问频率”不是独立变量,它和用户行为强耦合。比如设计软件的素材库,周五下午集中打开 PSD 文件,但迁移策略若按周均值算,会把它们常年锁在 HDD 上。
性能影响比想象中大:一次 MoveFileEx 调用在 HDD 上平均耗时 12–80ms(取决于碎片程度),而 SSD 是 0.3–2ms;但如果你每 5 秒检查一遍 top-10 热文件,这个轮询本身就会吃掉 5% CPU。
- 推荐替代方案:启动时用
FileStream带FileOptions.RandomAccess预读最近 3 天高频文件头 4KB,触发 OS 缓存预热,比物理迁移更轻量 - 对数据库文件、VM 虚拟盘这类长时独占型文件,永远禁止自动迁移——
FileStream.Lock会导致Move报ERROR_SHARING_VIOLATION - 如果程序本身支持配置文件路径(如 JSON 中的
"cache_root"),与其动态挪文件,不如让用户指定 SSD 路径,程序内部做软链接或符号链接路由










