找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 3321|回复: 1

【Windows】一种快速文件遍历方法的实现思路

[复制链接]

2

主题

5

回帖

5095

积分

用户组: 技术宅的结界VIP成员

UID
1854
精华
2
威望
7 点
宅币
5056 个
贡献
8 次
宅之契约
0 份
在线时间
7 小时
注册时间
2016-7-26
发表于 2019-5-26 22:59:29 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
本帖最后由 Mouri_Naruto 于 2019-5-26 23:02 编辑

附:对站长表示深深的感谢,给了我在这里发帖的机会。

先贴代码,毕竟一些人看帖的目的就是为了看代码,而且本文的重点并不是那些代码,而是我这样写的原因。
https://github.com/M2Team/NSudo/commit/0b4fc388baa54637e6a016bc5809ee6fbd946009
当然,由于自己当时比较仓促还没有对函数和结构体的定义添加参数,表示抱歉。

这个方案说白了就是使用 FileIdBothDirectoryRestartInfo 和 FileIdBothDirectoryInfo 这两个 FileInformationClass 调用 GetFileInformationByHandleEx API 来实现文件的定义。
原理并不复杂,而且我写的代码风格偏纯 C(虽然一些地方用的是 C++ 的特性),相信你们很快也能看懂。

于是,这篇文章主要是记录我那样设计的原因:

0、为什么使用偏纯 C 的语法?
答:有三个理由。一是偏纯 C 语法看起来比较清晰。二是不希望生成一些诸如栈回退处理程序、C++ 异常等类似的机器实现。三是希望做到 ABI 兼容以更方便地与自己开发的其他工具共享实现。

1、为什么不学习 Everything 遍历 USN?
答:USN 是 NTFS 文件系统的独有特性,并不是一种通用的解决方案。

2、为什么选择 GetFileInformationByHandleEx API?
答:虽然 GetFileInformationByHandleEx 内部关于 FileIdBothDirectoryRestartInfo 和 FileIdBothDirectoryInfo 这两个 FileInformationClass 的实现是直接调用 NtQueryDirectoryFile 实现的。但 NtQueryDirectoryFile 并不是完全文档化的 API,为了自己的其他项目更方便使用自己的实现,于是自从两年前我就尽量使用文档化 API。

3、为什么 FileInfoBuffer 的大小设置为 32768 字节?
答:其实,其实微软的文档并没有说明我们应该设置多大效率才能达到最高。.Net Core 的文件遍历类实现直接使用的 NtQueryDirectoryFile 缓冲区设置的是 4096 字节。我选择 32768 字节,主要是因为我曾经以遍历 C:\Windows\WinSxS\Manifests 目录做过一次实验。调用 GetFileInformationByHandleEx 的次数随着缓冲区大小的增加而减少,但是缓冲区大小超过 32768 字节后,调用 GetFileInformationByHandleEx 的次数就不会减少了,而且因为缓冲区变大,分配内存块时间增加,反而拖慢速度。

4、为什么不直接返回 FILE_ID_BOTH_DIR_INFO 结构体,而是返回自制的 M2_FILE_ENUMERATOR_INFORMATION 结构体?
答:我宁愿多一次内存拷贝,也要这么做,主要有两点原因。一是 FILE_ID_BOTH_DIR_INFO 结构体的 ShortName 和 FileName 这两个成员对应的字符串是没有 '\0' 终止标志,导致使用起来很难受。二是 FILE_ID_BOTH_DIR_INFO 结构体的一些成员显示出来要么失去了自己单独封装意义,要么用不到,要么用起来不够友好。

5、这样实现性能大概能提升多少?
答:完整遍历 4000 次 C:\Windows\WinSxS\Manifests 目录,相对系统的默认方式,也就是 FindFirstFile 和 FindNextFile 组合使用,大概有 30% 左右的速度提升。(从 151972ms 缩减到 112366ms)

6、为什么传入的是句柄而不是文件名?
答:作为 Dism++ 的核心开发者之一的我,势必会碰到要求进行大量文件操作的情况。譬如系统垃圾清理,就删除一个文件来说,至少要进行三种操作(获取文件的大小、去除文件的只读属性和删除文件)。如果直接使用系统提供的 API,其内部实现是这样的:
打开文件句柄 -> 获取文件大小 -> 关闭文件句柄 -> 打开文件句柄 -> 去除只读属性 -> 关闭文件句柄 -> 打开文件句柄 -> 添加删除标记 -> 关闭文件句柄

为了减少反复打开和关闭文件句柄的次数,做到以下的效果;而且也为了规范操作步骤,所以我搞了一套通过句柄操作文件的实现。
打开文件句柄 -> 获取文件大小 -> 去除只读属性 -> 添加删除标记 -> 关闭文件句柄

事实上,这样做后,在文件操作方面会有比较大的效率提升,至少能减少系统调用引起模式切换的次数。

很久没写技术文章了,手有点生疏了,大家见谅。

毛利
回复

使用道具 举报

0

主题

17

回帖

13

积分

用户组: 初·技术宅

UID
333
精华
0
威望
0 点
宅币
-4 个
贡献
0 次
宅之契约
0 份
在线时间
2 小时
注册时间
2014-6-4
发表于 2019-8-13 17:55:40 | 显示全部楼层
专业。。。。。。。。。。。。。。。。。。。
回复

使用道具 举报

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-3-29 10:14 , Processed in 0.033659 second(s), 30 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表