获取目录列表的最快方法是什么

时间:2017-06-20 16:20:26

标签: delphi winapi

我需要递归地为给定的根/父路径的目录构建树结构。 类似“浏览文件夹”对话框。

Delphi的public Dictionary<string,string>FindFirstFile API)无法使用FindFirst,而faDirectory将获取所有文件(无论指定的{FindNext如何都使用faAnyFile 1}})不仅是目录。这使构建树的过程非常变慢。

有没有快速的方法来获取目录列表(树)而不使用faDirectory / FindFirst

3 个答案:

答案 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

如果FindFirstFileExWFIND_FIRST_EX_LARGE_FETCHFindExInfoBasic一起使用,则会产生接近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;

如果这对你来说不够快,那么其他选项包括:

  1. 在Win7 +上,将FindFirstFileEx()FindExInfoBasicFIND_FIRST_EX_LARGE_FETCH一起使用。这将比FindFirstFile()提供速度提升。

  2. 直接访问文件系统元数据。在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;