简单了解NTFS文件系统中的MFT(Master File Table)主文件表,介绍GitHub开源项目NTFS-File-Search,统计Qt库和Windows Api函数,以及使用NTFS-File-Search项目中的方法读取盘符所有目录文件速度示例。
系列文章目录
整个专栏系列是根据GitHub开源项目NTFS-File-Search获取分区所有文件/目录列表的思路。
具体的如下:
- Qt/C++ 了解NTFS文件系统,了解MFT(Master File Table)主文件表(一)
介绍NTFS文件系统,对比通过MFT(Master File Table)主文件表获取数据的优劣,简单介绍开源项目NTFS-File-Search,以及了解借鉴NTFS系统中所参考的所有文章。- Qt/C++ 了解NTFS文件系统,解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址
读取$Boot引导分区扇区数据,获取单个簇大小(4096字节),$MFT元数据大小(1024字节)和$MFT元数据的起始簇号,计算出$MFT元数据在磁盘的偏移地址。- Qt/C++ 了解NTFS文件系统,获取首张MFT表数据,解析文件记录头内容找到第一个属性偏移地址
解析$MFT元数据结构,根据$MFT元数据文件记录头获取属性的偏移地址,用于后面解析0x80 $Data属性,获取Run List数据列表- Qt/C++ 了解NTFS文件系统,解析MFT主文件表中的常驻属性与非常驻属性
简单介绍$MFT元数据中的常驻属性与非常驻属性结构.- Qt/C++ 了解NTFS文件系统,解析0x80 $Data属性,获取Run List数据列表
根据0x80 $Data属性,找到存放所有$MFT元数据的区间列表(Run List数据列表)- Qt/C++ 了解NTFS文件系统,遍历Run Lists数据列表,读取0x30 $FILE_NAME属性,获取所有文件/目录数据
根据前面获取的 获取Run List数据列表,
遍历所有Run List数据列表读取所有$MFT元数据,并解析 $FILE_NAME属性和$STANDARD_INFORMATION属性获取文件或目录的实际大小,名称,磁盘分配大小,记录号 ,文件属性等信息- Qt/C++ 深入了解NTFS文件系统,解析0x90 $INDEX_ROOT和0xA0 $INDEX_ALLOCATION 属性,获取索引(例如目录)的B+树的根节点和子节点
解析x90 $INDEX_ROOT和0xA0 $INDEX_ALLOCATION 属性结构,获取索引(例如目录)的B+树的根节点和子节点,即可通过MFT RECORD ID定位到MFT RECORD表,加载当前MFT RECORD的子目录/文件数据.- Qt/C++ 深入了解NTFS文件系统,解析0x20 $ATTRIBUTE_LIST,获取MFT RECORD记录表的其他扩展属性和ID对应的地址偏移量
在尝试遍历NTFS文件系统中的所有文件目录时,发现包含很多子文件的文件夹,并没有0xA0 $INDEX_ALLOCATION 属性,只有个0x20 $ATTRIBUTE_LIST属性,于是通过解析x20 $ATTRIBUTE_LIST属性获取其他单个MFT RECORD表无法包含的扩展属性.并且通过扩展属性中的MFT RECORD ID找到属性所在的MFT RECORD记录表
前言
在工作中,遇到需要快速扫描电脑系统盘符文件目录的功能,在使用多线程优化扫描后,客户还是觉得不满意,说是WizTree软件几秒就能扫描完所有文档,
我一看,好家伙,我100Gb的硬盘50多万个的文件9.92秒全扫描完毕,盲猜是驱动级别开发,直说实现不了。
后来了解到是通过读取NTFS文件系统的MFT(Master File Table)表获取的文件目录列表.
MFT(Master File Table,主文件表)是Windows操作系统中NTFS(New Technology File System,新技术文件系统)的关键组成部分,用于存储文件和目录的元数据信息。MFT类似于Unix和Linux系统中的inode,但在实现上有所不同。
MFT包含以下信息:
- 文件和目录的属性:包括权限、所有者、创建时间、修改时间、访问时间等。
- 文件数据的物理位置:MFT记录了文件数据在磁盘上的存储位置。
- 文件名:MFT中存储了文件和目录的名称。
MFT的作用和重要性:
- 高效性:MFT提供了高效的文件系统索引机制,允许系统快速访问和管理文件。
- 节省空间:相比于在文件名中保存文件属性,使用MFT可以节省空间,尤其是在大量小文件存在的情况下。
- 支持硬链接:通过MFT,多个文件名可以指向同一个MFT条目,实现硬链接的功能。
- 支持文件系统的元数据管理:MFT存储了文件和目录的元数据信息,包括权限、所有者、时间戳等,这些信息对文件系统的管理和安全性至关重要。
- 提高系统稳定性:MFT的使用可以提高文件系统的稳定性和可靠性,使文件系统更加高效地管理文件和目录。
总的来说,MFT是NTFS文件系统的核心组成部分,对于Windows文件系统的性能和可靠性具有重要意义。
摘要出自:MFT(Master File Table,主文件表)是Windows操作系统中NTFS(New Technology File System,新技术文件系统)的关键组成部分,用于存储文件和目录的元数据信息。MFT类似于Unix和Linux系统中的inode,但在实现上有所不同。
一开始我是从 Everything原理及实现 了解到Master File Table (MTF)这个技术点,
奈何这篇文章作者是用JAVA写的示例,看不大懂,
于是在github开始找找有没有使用C/C++读取MFT表数据的示例;
最终找到一个:NTFS-File-Search
NTFS-File-Search
相对轻量级的应用程序,用于快速查询位于NTFS卷上的文件/目录。它通过读取和解析位于MFT(主文件表)中的文件记录来工作。
为Windows操作系统设计
虽然Linux支持NTFS驱动器,但这个程序是专门为Windows设计的。
大多数与linux不兼容的代码只是因为Win32 API数据类型。因此,将数据类型和(很少的)函数调用转换为它们支持的对应项应该不会太困难
!感谢大佬,膜拜大佬!
有了这个示例的代码以及结合各篇介绍MFT的文章,那么学习读取解析MFT表结构就变得简单了,后面所有读取MFT表数据的代码示例基本都是出自NTFS-File-Search。
实际对比
为了验证读取MFT表获取文件目录的数据有多快,我尝试使用QT自带的库和Windows Api函数来遍历系统盘文件目录,作一个对比。
1.使用Qt QDir获取所有文件/文件夹
使用QDir 遍历文件目录,代码如下(示例):
//! Qt版本统计文件或文件夹数量
void QTraversalDirectory(const QString dirPath)
{
QDir dir(dirPath);
//entryInfoList(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files)
QFileInfoList entries = dir.entryInfoList((QDir::Filters)(QDir::NoDotAndDotDot | QDir::AllDirs| QDir::Files),QDir::Name);
for (const QFileInfo &entry : entries) {
if (entry.isDir()) {
NumberFolders++;
// 递归遍历子目录
QTraversalDirectory(entry.absoluteFilePath());
} else {
NumberDocuments++;
AllocatedSize+=entry.size();
}
}
}
/*! 输出统计
QDateTime(2024-08-31 14:01:08.642 中国标准时间 Qt::LocalTime)
QDateTime(2024-08-31 14:02:24.099 中国标准时间 Qt::LocalTime)
75
[NumberFolders] 218370
[NumberDocuments] 452870
[AllocatedSize] 76198538442
*/
耗时大概75秒左右,而且也没有统计系统文件/文件夹,包括一些系统盘的隐藏文件夹或隐藏文件都没有统计,
2.使用Windows api函数获取盘符文件/文件夹
使用FindFirstFile 和 FindNextFile函数遍历文件目录
代码如下(示例):
void TraversalDirectory(QString dirPath)
{
//! 遍历通配符找到的相关文件
//! https://learn.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-findfirstfilew
QString rootdir=dirPath+"\\*";
WIN32_FIND_DATA fileData;
HANDLE hFind;
LPCWSTR searchPath=utf8_to_wchar(rootdir.toStdString().c_str());
hFind = FindFirstFile(searchPath, &fileData);
if (hFind != INVALID_HANDLE_VALUE) {
// 遍历文件
do {
if(fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
//! 文件夹目录
if((wcscmp(fileData.cFileName, L".") == 0 || wcscmp(fileData.cFileName, L"..") == 0))
continue;
QString driectory=QString::fromWCharArray(fileData.cFileName);
QString _rootpath=dirPath+"\\"+driectory;
TraversalDirectory(_rootpath);
NumberFolders+=1;
}
else
{
qint64 filesize= (qint64)(fileData.nFileSizeHigh * ((qint64)MAXDWORD+1)) + (qint64)fileData.nFileSizeLow;
AllocatedSize+=filesize;
NumberDocuments+=1;
}
} while (FindNextFile(hFind, &fileData));
// 关闭句柄
FindClose(hFind);
}
sfree(searchPath);
}
/*! 输出统计
QDateTime(2024-08-31 13:42:30.556 中国标准时间 Qt::LocalTime)
QDateTime(2024-08-31 13:46:00.995 中国标准时间 Qt::LocalTime)
210s
[NumberFolders] 247408
[NumberDocuments] 591151
[AllocatedSize] 114432276945
*/
耗时大概210秒左右,所有的隐藏文件和隐藏的文件夹都获取到了,但是很费时。
3.使用NTFS-File-Search示例遍历目录
将NTFS-File-Search项目示例移植到Qt开发环境中,调用获取C盘目录,简单统计
代码如下(示例):
void Parint_ALLMFT()
{
const WCHAR szVolume[7] = { L'\\', L'\\', L'.', L'\\', L'C', L':' };
qDebug()<<"[szVolume] "<<QString::fromWCharArray(szVolume);
QDateTime StartTime=QDateTime::currentDateTime();
QDateTime endt=QDateTime::currentDateTime();
qint64 NumberFolders=0;
qint64 NumberDocuments=0;
PNTFS_FILE_ENTRY pFileResults;
UINT64 nResultCount;
g_pSourceVolume = new NTFS_Volume();
if (!(g_pSourceVolume->Open(szVolume) && g_vSearcher.SetVolume(g_pSourceVolume)))
{
goto error;
}
//清除筛选条件
g_vSearcher.ClearFileFilters();
BOOL bSuccess = g_vSearcher.FindFiles(FILE_SEARCH_FLAG_FIND_ALL,
&pFileResults,
&nResultCount
);
PNTFS_FILE_ENTRY pEnumResult = pFileResults;
qDebug()<<" nResultCount : "<<nResultCount;
for (UINT64 i = 0; i < nResultCount; ++i)
{
// qDebug()<<"[MFT Record Number "<<QString::number(pEnumResult->MFTFileId.MftRecordIndex,10)<<"] -> "<<QString::fromWCharArray(pEnumResult->lpszFileName);
if (pEnumResult->NextEntryOffset == 0) {
break;
}
if(pEnumResult->IsDirectory)
NumberFolders++;
else
NumberDocuments++;
pEnumResult = POINTER_ADD(PNTFS_FILE_ENTRY, pFileResults, pEnumResult->NextEntryOffset);
}
endt=QDateTime::currentDateTime();
qDebug()<<"Start : "<<StartTime;
qDebug()<<"End : "<<endt;
qDebug()<<StartTime.secsTo(endt)<<"s";
qDebug()<<"[NumberFolders] " <<QString::number(NumberFolders,10);
qDebug()<<"[NumberDocuments] "<<QString::number(NumberDocuments,10);
goto out;
error:
qDebug("Error: failed to search volume files\n");
out:
/* Close the volume */
g_pSourceVolume->Close();
delete g_pSourceVolume;
}
/*
Start : QDateTime(2024-09-12 11:46:59.229 中国标准时间 Qt::LocalTime)
End : QDateTime(2024-09-12 11:47:09.505 中国标准时间 Qt::LocalTime)
10s
[NumberFolders] "282110"
[NumberDocuments] "641776"
*/
耗时十几秒,获取了C盘所有的文件目录,这扫描速度确实快…
总结
通过读取MFT(Master File Table)主文件表,获取盘符文件目录结构,确实是最快的
可能由于NTFS版本等等问题,NTFS-File-Search示例中读取文件的分配大小和实际大小存在问题,也出现过无效文件的问题,只有通过QFileInfo重新获取文件大小。总体上是不影响使用,建议多参考借鉴学习。
参考文章
- NTFS系统文件 : www.ntfs.com
- MFT(Master File Table,主文件表)是Windows操作系统中NTFS(New Technology File System,新技术文件系统)的关键组成部分,用于存储文件和目录的元数据信息。MFT类似于Unix和Linux系统中的inode,但在实现上有所不同。
- 关于NTFS-MFT
- Windows NTFS文件系统原理深度剖析
- Windows NTFS文件系统原理深度剖析(中篇)
- NTFS文件系统详解(三)之NTFS元文件解析
- 分析NTFS系统,得到特定文件内容
- NTFS文件系统详解
- 「NTFS:让你的硬盘更安全、更高效!」NTFS文件系统详解
下一篇:Qt/C++ 了解NTFS文件系统,解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址