我需要递归地为给定的根/父路径的仅目录构建树结构。 类似“浏览文件夹”对话框。
Delphi的public Dictionary<string,string>
(FindFirstFile
API)无法使用FindFirst
,而faDirectory
将获取所有文件(无论指定的{FindNext
如何都使用faAnyFile
1}})不仅是目录。这使构建树的过程非常变慢。
有没有快速的方法来获取目录列表(树)而不使用faDirectory
/ FindFirst
?
答案 0 :(得分:6)
绝对最快的方式,使用NtQueryDirectoryFile
api。有了这个,我们可以一次查询单个文件,但查询多个文件。还可以选择要返回的信息(较小的信息 - 更高的速度)。示例(使用完全递归)
// int nLevel, PSTR prefix for debug only
void ntTraverse(POBJECT_ATTRIBUTES poa, int nLevel, PSTR prefix)
{
enum { ALLOCSIZE = 0x10000 };//64kb
if (nLevel > MAXUCHAR)
{
DbgPrint("nLevel > MAXUCHAR\n");
return ;
}
NTSTATUS status;
IO_STATUS_BLOCK iosb;
UNICODE_STRING ObjectName;
OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName };
DbgPrint("%s[<%wZ>]\n", prefix, poa->ObjectName);
if (0 <= (status = NtOpenFile(&oa.RootDirectory, FILE_GENERIC_READ, poa, &iosb, FILE_SHARE_VALID_FLAGS,
FILE_SYNCHRONOUS_IO_NONALERT|FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT)))
{
if (PVOID buffer = new UCHAR[ALLOCSIZE])
{
union {
PVOID pv;
PBYTE pb;
PFILE_DIRECTORY_INFORMATION DirInfo;
};
while (0 <= (status = NtQueryDirectoryFile(oa.RootDirectory, NULL, NULL, NULL, &iosb,
pv = buffer, ALLOCSIZE, FileDirectoryInformation, 0, NULL, FALSE)))
{
ULONG NextEntryOffset = 0;
do
{
pb += NextEntryOffset;
ObjectName.Buffer = DirInfo->FileName;
switch (ObjectName.Length = (USHORT)DirInfo->FileNameLength)
{
case 2*sizeof(WCHAR):
if (ObjectName.Buffer[1] != '.') break;
case sizeof(WCHAR):
if (ObjectName.Buffer[0] == '.') continue;
}
ObjectName.MaximumLength = ObjectName.Length;
if (DirInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
ntTraverse(&oa, nLevel + 1, prefix - 1);
}
} while (NextEntryOffset = DirInfo->NextEntryOffset);
if (ALLOCSIZE - iosb.Information > (ULONG)FIELD_OFFSET(FILE_DIRECTORY_INFORMATION, FileName[256]))
{
break;//NO_MORE_FILES
}
}
delete [] buffer;
if (status == STATUS_NO_MORE_FILES)
{
status = STATUS_SUCCESS;
}
}
NtClose(oa.RootDirectory);
}
if (0 > status)
{
DbgPrint("---- %x %wZ\n", status, poa->ObjectName);
}
}
void ntTraverse()
{
BOOLEAN b;
RtlAdjustPrivilege(SE_BACKUP_PRIVILEGE, TRUE, FALSE, &b);
char prefix[MAXUCHAR + 1];
memset(prefix, '\t', MAXUCHAR);
prefix[MAXUCHAR] = 0;
STATIC_OBJECT_ATTRIBUTES(oa, "\\systemroot");
ntTraverse(&oa, 0, prefix + MAXUCHAR);
}
但是如果你使用交互式树 - 你不需要一次扩展所有树,但只需要顶层,用TVN_ITEMEXPANDING
处理TVE_EXPAND
,用TVN_ITEMEXPANDED
处理TVE_COLLAPSE
进行展开用户点击/折叠节点并设置cChildren
如果FindFirstFileExW
与FIND_FIRST_EX_LARGE_FETCH
和FindExInfoBasic
一起使用,则会产生接近NtQueryDirectoryFile
的效果,但会小一些:
WIN32_FIND_DATA fd;
HANDLE hFindFile = FindFirstFileExW(L"..\\*", FindExInfoBasic, &fd, FindExSearchLimitToDirectories, 0, FIND_FIRST_EX_LARGE_FETCH);
if (hFindFile != INVALID_HANDLE_VALUE)
{
do
{
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (fd.cFileName[0] == '.')
{
switch (fd.cFileName[1])
{
case 0:
continue;
case '.':
if (fd.cFileName[2] == 0) continue;
break;
}
}
DbgPrint("%S\n", fd.cFileName);
}
} while (FindNextFile(hFindFile, &fd));
FindClose(hFindFile);
}
遗憾的是FindExSearchLimitToDirectories
目前未实施
答案 1 :(得分:5)
Find(First|Next)/File()
是一个可行的解决方案,特别是在Delphi 7中。只需过滤掉您不需要的结果,例如:
if FindFirst(Root, faDirectory, sr) = 0 then
try
repeat
if (sr.Attr and faDirectory <> 0) and (sr.Name <> '.') and (sr.Name <> '..') then
begin
// ...
end;
until FindNext(sr) <> 0;
finally
FindClose(sr);
end;
如果这对你来说不够快,那么其他选项包括:
在Win7 +上,将FindFirstFileEx()
与FindExInfoBasic
和FIND_FIRST_EX_LARGE_FETCH
一起使用。这将比FindFirstFile()
提供速度提升。
直接访问文件系统元数据。在NTFS上,您可以使用DeviceIoControl()
直接枚举Master File Table。
答案 2 :(得分:0)
如果你有Delphi XE2或更新版本,最快的方法是使用TDirectory.GetDirectories
中定义的System.IOUtils
。
示例代码:
procedure TVideoCamera.GetInterfaceNameList(
const AInterfaceNameList: TInterfaceNameList);
const
SEARCH_OPTION = TSearchOption.soTopDirectoryOnly;
PREDICATE = nil;
var
interfaceList: TStringDynArray;
idxInterface: Integer;
interfaceName: String;
begin
interfaceList := TDirectory.GetDirectories(GetCameraDirectory, SEARCH_OPTION,
PREDICATE);
AInterfaceNameList.Clear;
for idxInterface := Low(interfaceList) to High(interfaceList) do
begin
interfaceName := ExtractFileName(InterfaceList[idxInterface]);
AInterfaceNameList.Add(interfaceName);
end;
end;