本文整理自网络,侵删。
USN是Update Service Number Journal or Change Journal的英文缩写,直译为“更新序列号”,是对NTFS卷里所修改过的信息进行相关记录的功能。当年微软发布Windows 2000时,建立NTFS 5.0的同时,加入了一些新功能和改进了旧版本的文件系统,为它请来了一位可靠的秘书,它可以在分区中设置监视更改的文件和目录的数量,记录下监视对象修改时间和修改内容。没错,它就是USN日志。当这个功能启用时,对于每一个NTFS卷,当发生有关添加、删除和修改文件的信息时,NTFS都使用USN日志记录下来。
真高效!探访秘书的工作
NTFS秘书――USN日志的工作方式,相对来说很简单,所以非常的高效。它开始的时候是一个空文件,包括NTFS每个卷的信息。每当NTFS卷有改变的时候,所改变的信息会马上被添加到这个文件里。这其中,每条修改的记录都使用特定符号来标识为日志形式,也就是USN日志。每条日志,记录了包括文件名、文件信息做出的改变。怎样在系统中让秘书开始干活儿呢?如图2所示,在NTFS分区的图标上右击选择“属性”,勾选圈中部分即可。
忠诚的秘书只为NTFS效劳
USN秘书不仅工作高效,而且非常的忠诚,虽然这种忠诚看起来有点迫不得已。日志里包括发生了什么变化(添加、删除或其他操作),但并不会记录数据或其他变化的细节,所以它只能工作在NTFS文件系统中。
看到上面的描述,你也许还是比较难以理解,那么就举个例子说明一下。USN日志为什么不能在FAT32文件系统下运用呢?就像钢笔不能在宣纸上记录,只能在普通纸上记录一样。USN日志相当于一本书的索引,当然书里面内容发生添加、修改或删除的时候,USN日志会记录下来何时做了修改,并使用特定序列号来标识,但它并不会记录里面具体修改了什么东西,所以索引文件很小。而当你想查找某一篇文章时,你就不用一页一页去翻书,可以直接通过查找USN日志(也就是建立的索引)就知道这篇文章是否存在。
我在第一次使用 Everything 时,对其速度确实感到惊讶,后来了解到是通过操作 USN 实现的,并且有一定的局限性(只有 NTFS 下才能使用)。USN的使用(Everything的快不单是用了USN,还需要建立索引)
整个实现分为 6 步:1. 判断驱动盘是否为 NTFS 格式2. 获取驱动盘句柄3. 初始化 USN 日志文件4. 获取 USN 基本信息5. 列出 USN 日志的所有数据6. 删除 USN 日志文件
对于 NTFS 磁盘文件,可以通过遍历USN日志来搜索,可以非常快查找文件,上图为我的电脑 160多万 文件,大概为 10秒
以下代码在 Delphi 10.3 通过, 有兴趣的同学们 可以直接复制 使用。
注意:请使用管理员权限 运行
主程序调用
procedure TForm1.Button1Click(Sender: TObject);var Strs : TStringList;begin
Strs:= TStringList.Create; Strs.BeginUpdate; SearchNTFS('D', Strs); Strs.SaveToFile(ExtractFilePath(ParamStr(0))+ ' D盘文件.txt', TEncoding.UTF8); Strs.EndUpdate; Strs.Free;
Showmessage('搜索完成');end;
/****************************************************
wwww.DelphiFmx.com 赵客, 转载请注明出处
****************************************************/
unit uSearchNTFS;
interface
uses System.Classes;
function SearchNTFS(const ADiskName: WideChar; AFiles: TStrings):Boolean;
implementation
uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, Generics.Collections, Vcl.Dialogs;
/*************** wwww.DelphiFmx.com 赵客 ***************/
type _PARTITION_INFORMATION = record StartingOffset :LARGE_INTEGER;// 指定分区开始的驱动器上的字节偏移量 PartitionLength:LARGE_INTEGER;// 指定分区的长度(以字节为单位) HiddenSectors: Cardinal; // 指定隐藏扇区的数量 PartitionNumber: Cardinal; // 指定分区号 PartitionType: Byte; // 分区类型 BootIndicator: Boolean; // TRUE时,指示此分区是该设备的可引导(活动)分区。为FALSE时,该分区不可引导 RecognizedPartition: Boolean; // TRUE时,表示系统识别分区的类型。为FALSE时,系统无法识别分区的类型 RewritePartition: Boolean; // TRUE时,表明分区信息已更改。为FALSE时,分区信息未更改 end; PARTITION_INFORMATION = _PARTITION_INFORMATION; PPARTITION_INFORMATION = ^_PARTITION_INFORMATION;
// 单条USN记录结构信息结构 _USN_RECORD = record RecordLength: DWORD; // 该条USN记录长度 MajorVersion: WORD; // 主版本 MinorVersion: WORD; // 次版本 FileReferenceNumber: DWORDLONG; // 文件引用数 ParentFileReferenceNumber: DWORDLONG;// 父文件引用数 Usn: Int64; // USN(一般为int64类型) TimeStamp: LARGE_INTEGER; // 时间戳 Reason: DWORD; // 原因 SourceInfo: DWORD; // 源信息 SecurityId: DWORD; // 安全 FileAttributes: DWORD; // 文件属性(文件或目录) FileNameLength: WORD; // 文件名长度 FileNameOffset: WORD; // 文件名偏移量 FileName: PWideChar; // 文件名第一位的指针 end; USN_RECORD = _USN_RECORD; PUSN_RECORD=^_USN_RECORD;
// USN日志信息结构 _USN_JOURNAL_DATA= record UsnJournalID: DWORDLONG; // USN日志ID FirstUsn: Int64; // 第一条USN记录的位置 NextUsn: Int64; // 下一条USN记录将要写入的位置 LowestValidUsn: Int64; // 最小的有效的USN(FistUSN小于该值) MaxUsn: Int64; // USN最大值 MaximumSize: DWORDLONG; // USN日志最大大小(按Byte算) AllocationDelta: DWORDLONG;// USN日志每次创建和释放的内存字节数 end; USN_JOURNAL_DATA = _USN_JOURNAL_DATA; PUSN_JOURNAL_DATA =^_USN_JOURNAL_DATA;
// 创建USN日志的结构 _CREATE_USN_JOURNAL_DATA = record MaximumSize: DWORDLONG; // NTFS文件系统分配给USN日志的最大大小(字节) AllocationDelta: DWORDLONG;// USN日志每次创建和释放的内存字节数 end; CREATE_USN_JOURNAL_DATA = _CREATE_USN_JOURNAL_DATA; PCREATE_USN_JOURNAL_DATA = ^_CREATE_USN_JOURNAL_DATA;
// 删除USN日志的结构 _DELETE_USN_JOURNAL_DATA = record UsnJournalID: DWORDLONG;// USN日志ID DeleteFlags: DWORD; // 删除标志 end; DELETE_USN_JOURNAL_DATA = _DELETE_USN_JOURNAL_DATA; PDELETE_USN_JOURNAL_DATA = ^_DELETE_USN_JOURNAL_DATA;
// 遍历USN记录时的结构 _MFT_ENUM_DATA = record StartFileReferenceNumber: DWORDLONG;// 开始文件引用数,第一次调用必须为0 LowUsn: Int64; // 最小USN,第一次调用,最好为0 HighUsn: Int64; // 最大USN end; MFT_ENUM_DATA = _MFT_ENUM_DATA; PMFT_ENUM_DATA = ^_MFT_ENUM_DATA;
// 获取USN日志变更的结构 _READ_USN_JOURNAL_DATA= record StartUsn: Int64; // 变更的USN记录开始位置,即第一次读取USN日志的LastUsn值。 ReasonMask: DWORD; // 原因标识 ReturnOnlyOnClose: DWORD; // 只有在记录关闭时才返回 Timeout: DWORDLONG; // 延迟时间 BytesToWaitFor: DWORDLONG; // 当USN日志大小大于该值时返回 UsnJournalID: DWORDLONG; // USN日志ID end; READ_USN_JOURNAL_DATA = _READ_USN_JOURNAL_DATA; PREAD_USN_JOURNAL_DATA = ^_READ_USN_JOURNAL_DATA;
// 文件定义 _FileInfo = record FileName: String; // 文件名称 FileId: UInt64; // 文件的ID ParentId: UInt64; // 文件的父ID end; TFileInfo = _FileInfo; PFileInfo = ^_FileInfo;
// TStringList 按数值排序函数function MySort(List: TStringList; Index1, Index2: Integer): Integer;var L, R: Int64;begin L := PFileInfo(List.Objects[Index1])^.FileId; R := PFileInfo(List.Objects[Index2])^.FileId; if L < R then begin Result := -1 end else if L = R then begin Result := 0 end else begin Result := 1; end;end;
// 获取文件全路径,包含路径和文件名procedure GetFullFileName(const DiskName: WideChar; Files: TStringList);var ArrU64: TArray; Idx : Integer; ParentId: UInt64; Item: Integer;begin // 将 FileList 按 FileReferenceNumber 数值排序 Files.Sorted := False; Files.CustomSort(MySort);
// 将排序好的 FileReferenceNumber 复制到 UInt64 数组列表中,便于下面进行快速查找 SetLength(ArrU64, Files.Count); for Idx := 0 to Files.Count - 1 do begin ArrU64[Idx] := PFileInfo(Files.Objects[Idx])^.FileId; end;
// 获取每一个文件全路径名称 for Idx := 0 to Files.Count - 1 do begin ParentId := PFileInfo(Files.Objects[Idx])^.ParentId; while TArray.BinarySearch(ArrU64, ParentId, Item) do begin ParentId := PFileInfo(Files.Objects[Item])^.ParentId; Files.Strings[Idx] := PFileInfo(Files.Objects[Item])^.FileName + '\' + Files.Strings[Idx]; end; Files.Strings[Idx] := (DiskName + ':\' + Files.Strings[Idx]); end;end;
function SearchNTFS(const ADiskName: WideChar; AFiles: TStrings): Boolean;const Len_Buf = 440 * 1024; // 500 PARTITION_IFS = $07; USN_DeleteFlags= $00000001;var lpMaxiLen, lpFileFlags: DWORD; Path: Array [0..MAX_PATH-1] of WideChar;
ErrStr : String;
hTmp : THandle; hFile: THandle;
pInfo : PARTITION_INFORMATION; dwRet: DWORD;
CUSN : CREATE_USN_JOURNAL_DATA; //创建 USN : USN_JOURNAL_DATA; DUSN : DELETE_USN_JOURNAL_DATA; //删除
MEnum : MFT_ENUM_DATA; Int64SZ: UInt; Buf : array[0 .. Len_Buf-1] of WideChar; UsnRecord: PUSN_RECORD; AFileName: String;
pFile: PFileInfo; Idx: Integer;begin Result := False; // 1.是否是NTFS磁盘格式 GetVolumeInformation(PWideChar(ADiskName+':\') // 磁盘驱动器代码字符串 , nil // 磁盘驱动器卷标名称 , 0 // 磁盘驱动器卷标名称长度 , nil // 磁盘驱动器卷标序列号 , lpMaxiLen // 系统允许的最大文件名长度 , lpFileFlags// 文件系统标识 , Path // 文件操作系统名称 , MAX_PATH // 文件操作系统名称长度 );
if SameText(Path, 'NTFS') then begin // 2.打开卷 (需要管理员权限) 管理员权限启动方法 请参考 http://delphifmx.com/zh-hans/node/6 hTmp := 0; hFile:= Winapi.Windows.CreateFile(PWideChar('\\.\'+ ADiskName+':') , Generic_Read + Generic_Write // 打开卷 操作方式(读或写) , File_Share_Read + File_Share_Write// 共享方式 , nil , Open_Existing // 文件创建方法 , File_Attribute_ReadOnly // 文件属性 , hTmp); if hFile = INVALID_HANDLE_VALUE then begin ErrStr := SysErrorMessage(GetLastError()); Vcl.Dialogs.ShowMessage(ErrStr); Exit; end; // 3.初始化USN日志文件 if not DeviceIoControl(hFile, FSCTL_CREATE_USN_JOURNAL, @CUSN, Sizeof(CUSN), nil, 0, dwRet, nil) then begin ErrStr := SysErrorMessage(GetLastError()); Vcl.Dialogs.ShowMessage(ErrStr); Exit; end; // 4.获取USN日志基本信息 if not DeviceIoControl(hFile, FSCTL_QUERY_USN_JOURNAL, nil, 0, @USN, Sizeof(USN), dwRet, nil) then begin ErrStr := SysErrorMessage(GetLastError()); Vcl.Dialogs.ShowMessage(ErrStr); Exit; end; // 5.枚举USN日志文件中的所有记录 MEnum.StartFileReferenceNumber := 0; MEnum.LowUsn := 0; MEnum.HighUsn := USN.NextUsn; Int64SZ:= Sizeof(Int64); while DeviceIoControl(hFile, FSCTL_ENUM_USN_DATA, @MEnum, Sizeof(MEnum), @Buf, Len_Buf, dwRet, nil) do begin // 找到第一个 USN 记录 UsnRecord := PUSN_RECORD( Integer(@Buf) + Int64SZ ); while dwRet > 60 do begin // 获取文件名称 AFileName := PWideChar(Integer(UsnRecord) + UsnRecord^.FileNameOffset); AFileName := Copy(AFileName, 1, UsnRecord^.FileNameLength div 2); // 将文件信息添加到列表中 pFile := AllocMem(Sizeof(TFileInfo)); // 注意要释放内存 pFile^.FileName := AFileName; pFile^.FileId := UsnRecord^.FileReferenceNumber; pFile^.ParentId := UsnRecord^.ParentFileReferenceNumber; AFiles.AddObject(AFileName, TObject(pFile)); // 获取下一个 USN 记录 if UsnRecord.RecordLength > 0 then begin Dec(dwRet, UsnRecord.RecordLength) end else begin Break; end; UsnRecord := PUSN_RECORD(Cardinal(UsnRecord) + UsnRecord.RecordLength); end; Move(Buf, MEnum, Int64SZ); end; // 7.获取文件全路径,包含路径和文件名 GetFullFileName(ADiskName, TStringList(AFiles)); // 8.释放内存 for Idx := AFiles.Count - 1 Downto 0 do begin FreeMem(PFileInfo(AFiles.Objects[Idx])); end; // 9.删除USN日志文件信息 DUSN.UsnJournalID := USN.UsnJournalID; DUSN.DeleteFlags := USN_DeleteFlags; DeviceIoControl(hFile, FSCTL_DELETE_USN_JOURNAL, @DUSN, Sizeof(DUSN), nil, 0, dwRet, nil); CloseHandle(hFile); Result := True; end;
end;
end.
来源http://delphifmx.com/node/13
相关阅读 >>
Delphi xe6 取得app自己的版本号(横跨4个平台)
Delphi readprocessmemory 输入进程id 输入读取地址
更多相关阅读请进入《Delphi》频道 >>