Windows Practice_文件搜索器(一)递归

来源:互联网 发布:海大富 知乎 编辑:程序博客网 时间:2024/06/11 19:41

背景

由于Windows自带的文件搜索的搜索速度比较慢,所以我们需要自己写一个文件搜索器来提高文件的扫描速度。

文件搜索需要用到两个Windows API,一个是FindFirstFile,另一个是FindNextFile函数。
首先我们先来看一下这两个函数的原型:

HANDLE WINAPI FindFirstFile(  _In_  LPCTSTR           lpFileName,  _Out_ LPWIN32_FIND_DATA lpFindFileData);

lpFileName要搜索的目录或者路径。需要注意的是后面还需要加上过滤条件,比如,我们想搜索C盘中所有的文件或者文件夹,这个参数就应该是:C:\*.*。其它的就没有什么需要注意的了!!!
lpFindFileData,这是一个输出参数,它包含了找到的文件或者目录的信息。
返回值如果函数执行成功,它返回的是一个搜索句柄,如果失败,返回 INVALID_HANDLE_VALUE。

typedef struct _WIN32_FIND_DATA {  DWORD    dwFileAttributes;  FILETIME ftCreationTime;  FILETIME ftLastAccessTime;  FILETIME ftLastWriteTime;  DWORD    nFileSizeHigh;  DWORD    nFileSizeLow;  DWORD    dwReserved0;  DWORD    dwReserved1;  TCHAR    cFileName[MAX_PATH];  TCHAR    cAlternateFileName[14];} WIN32_FIND_DATA, *PWIN32_FIND_DATA, *LPWIN32_FIND_DATA;

dwFileAttributes文件或目录的属性。
ftCreationTime文件或者目录创建的时间。
ftLastAccessTime对于一个文件,它记录了最后一次读、写或者执行的时间;对于一个目录,它记录了这个目录是什么时候被创建的。
ftLastWriteTime对于一个文件,它记录了最后一次写入,截断、=或者重写的时间;对于一个目录,它记录了这个目录是什么时候被创建的。
nFileSizeHigh文件大小的高阶段,以字节为单位。
nFileSizeLow文件大小的低阶段,以字节为单位。
dwReserved0如果dwFileAttributes 包含了FILE_ATTRIBUTE_REPARSE_POINT 属性, 该成员指定重新解析点标记,否则这个成员的值是未定义的,并且也不该使用。
dwReserved1被保留,用于将来使用。
cFileName[MAX_PATH]文件名字。
cAlternateFileName[14]该文件的后缀名。

BOOL WINAPI FindNextFile(  _In_  HANDLE            hFindFile,  _Out_ LPWIN32_FIND_DATA lpFindFileData);

hFindFile由 FindFirstFile函数返回的搜索句柄。
lpFindFileData这是一个输出参数,它包含了找到的文件或者目录的信息。
返回值如果函数执行成功,它返回的是一个非零值,并且lpFindFileData结构体被填充,否则返回零。

递归实现方式

我们的第一个版本是使用递归的方式进行

#include <Windows.h>#include <string>#include <stdio.h>DWORD g_dwFindFileNumber = 0, g_dwSearchFileNumber = 0, g_dwSearchDirectoryNumber = 0;std::wstring MakeStandardDir(std::wstring wstrDir){    if (wstrDir[wstrDir.size()-1] != '\\')    {        wstrDir += '\\';    }    return wstrDir;}void MyFindFile(std::wstring wstrBeginDir, std::wstring wstrFindFileName){    WIN32_FIND_DATA findData = { 0 };    HANDLE hFile = FindFirstFileW((MakeStandardDir(wstrBeginDir) + L"*.*").c_str(), &findData);    do    {        if (wcscmp(findData.cFileName, L".") == 0)        {            continue;        }        if (wcscmp(findData.cFileName, L"..") == 0)        {            continue;        }        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)        {            //printf("%ls 是一个目录!\r\n", findData.cFileName);            MyFindFile((MakeStandardDir(wstrBeginDir) + findData.cFileName).c_str(), wstrFindFileName);            ++g_dwSearchDirectoryNumber;        }        else        {            //printf("%ls 是一个文件!\r\n", findData.cFileName);            if (wcsstr(findData.cFileName, wstrFindFileName.c_str()) != nullptr)            {                printf("%ls%ls\r\n", wstrBeginDir.c_str(), findData.cFileName);                ++g_dwSearchFileNumber;                ++g_dwFindFileNumber;            }            ++g_dwSearchFileNumber;        }    }    while (FindNextFileW(hFile, &findData));}int main(){    std::wstring wstrBeginDir = L"C:";    std::wstring wstrFileName = L"ntdll";    DWORD dwPrevTime = GetTickCount();    MyFindFile(wstrBeginDir, wstrFileName);    DWORD dwTimeUse = (GetTickCount() - dwPrevTime)/1000;    printf("\r\n\r\n");    printf("共找到 %lu 个 %ls 相关的文件!\r\n", g_dwFindFileNumber, wstrFileName.c_str());    printf("共查找了 %lu 个文件夹, %lu 个文件!\r\n", g_dwSearchDirectoryNumber, g_dwSearchFileNumber);    printf("用时 %lu 秒\r\n", dwTimeUse);    system("pause");    return 0;}

执行结果如下所示:
这里写图片描述

上面的程序需要注意以下几点:

  • 条件判断(组合值)
应该是if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY),而不该是if(findData.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY),因为这里比较的是位,就不多做解释了。
  • 过滤条件
在传入的第一个目录时,应该是“C:\\*.*”,而不应该只是“C:\\”,还需要加上后面的“*.*”的过滤条件。
  • 遇到文件和文件夹这两种情况时候的应该做相应的处理
当遇到文件的时候,就使用模糊匹配,如果包含我们的搜索条件,那么就是我们要找的文件;当遇到文件夹的时候,就需要进入文件夹(进入文件夹的时候还需要注意加上"*.*",这个过滤条件),从而扫描文件夹里面的所有文件或者文件夹,这就是递归的使用方法。
  • 路径
对于文件夹名,它的后面是没有"\\",我们需要给它加上,这事是我们上面写的MakeStandardDir函数。
  • 特殊的目录
无论是在Linux系统还是在Windows系统中,每个文件夹中都会有一个"."目录和一个".."目录,"."表示当前目录,".."表示上一级目录。

“多线程”实现方式

细心的朋友看到这个标题的时候肯定会想,多线程就多线程吧,为什么还要加上一个双引号呢?难道不是真正的多线程,而是一个伪多线程吗?…
其实它是一个真正的多线程,只不过启用的线程太多了而已。反正是不正常的多线程。实现的逻辑是这样的:
我们每遇到一个目录就开启一个线程来负责扫描本线程中的文件,这样看起来好像很完美呀!你看我开这么多的线程来同时扫描你一个小小的硬盘,那速度肯定也是没谁了!!!我们先不要下结论,先按照我们的思路把代码实现,然后运行一次看看结果再下结论吧!

#include <string>#include <stdio.h>#include <process.h>#include <Windows.h>HANDLE g_hExitEvent;CRITICAL_SECTION g_cs;long g_lFindFileNumber = 0, g_lSearchFileNumber = 0, g_lSearchDirectoryNumber = 0, g_lNeedSearchDirNum = 0;std::wstring MakeStandardDir(std::wstring wstrDir){    if (wstrDir.back() != L'\\')    {        wstrDir += L'\\';    }    return wstrDir;}struct ThreadData{    std::wstring wstrDirectoryName;    std::wstring wstrSearch;    std::wstring wstrFilter;};unsigned int __stdcall ThreadFindFile(void *lParam){    InterlockedAdd(&g_lNeedSearchDirNum, 1);    ThreadData *pData = (ThreadData *)lParam;    WIN32_FIND_DATA findData = { 0 };    HANDLE hFile = FindFirstFileW((MakeStandardDir(pData->wstrDirectoryName) + pData->wstrFilter).c_str(), &findData);    do    {        if (wcscmp(findData.cFileName, L".") == 0)        {            continue;        }        if (wcscmp(findData.cFileName, L"..") == 0)        {            continue;        }        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)        {            ThreadData *pTempData = new ThreadData;            pTempData->wstrDirectoryName = MakeStandardDir(pData->wstrDirectoryName) + findData.cFileName;            pTempData->wstrFilter = pData->wstrFilter;            pTempData->wstrSearch = pData->wstrSearch;            EnterCriticalSection(&g_cs);            CloseHandle((HANDLE)_beginthreadex(nullptr, 0, ThreadFindFile, pTempData, 0, nullptr));            LeaveCriticalSection(&g_cs);            InterlockedAdd(&g_lSearchDirectoryNumber, 1);        }        else        {            if (wcsstr(findData.cFileName, pData->wstrSearch.c_str()) != nullptr)            {                EnterCriticalSection(&g_cs);                printf("%ls%ls\r\n", pData->wstrDirectoryName.c_str(), findData.cFileName);                LeaveCriticalSection(&g_cs);                InterlockedAdd(&g_lFindFileNumber, 1);            }            InterlockedAdd(&g_lSearchFileNumber, 1);        }    } while (FindNextFileW(hFile, &findData));    delete pData;    InterlockedAdd(&g_lNeedSearchDirNum, -1);    if (g_lNeedSearchDirNum == 0)    {        SetEvent(g_hExitEvent);    }    return 0;}int main(){    InitializeCriticalSection(&g_cs);    g_hExitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);    ThreadData *pData = new ThreadData;    pData->wstrDirectoryName = L"C:";    pData->wstrFilter = L"*.*";    pData->wstrSearch = L"ntdll";    DWORD dwPrevTime = GetTickCount();    CloseHandle((HANDLE)_beginthreadex(nullptr, 0, ThreadFindFile, pData, 0, nullptr));    WaitForSingleObject(g_hExitEvent, INFINITE);    DWORD dwTimeUse = (GetTickCount() - dwPrevTime) / 1000;    printf("共找到 %lu 个 ntdll 相关的文件!\r\n", g_lFindFileNumber);    printf("共查找了 %lu 个文件夹, %lu 个文件!\r\n", g_lSearchDirectoryNumber, g_lSearchFileNumber);    printf("用时 %lu 秒\r\n", dwTimeUse);    DeleteCriticalSection(&g_cs);    system("pause");    return 0;}

以上代码已经准备好,我们就开始测试吧!!!期待中~
这里写图片描述
看到这个结果是不是很惊讶!!!首先结果跟我们的递归方式扫描的结果不一样啊!!!怎么无缘无故少了4个???还有扫描时间竟然是216秒,是递归扫描用时34秒的6倍多,这还是多线程吗?简直就是没法玩了!!!
这是哪里出问题了呢?可是我们的逻辑也是经过谨慎的思考了呀。
先不要着急,我们再来运行一下看看结果:
这里写图片描述
这次运行结果怎么时间也短了,最重要的是貌似我们要找的结果好像也对了, 但是这并非是个正确的结果,因为查找到的文件个数和文件夹个数和递归扫描的结果不相等,所以这次的扫描也是不完整的。
下面我们来分析一下为什么会出现这样的结果:
首先,我们创建线程的时候,有可能会失败,这个我们没有考虑到,下面我们看一下这个错误结果。

//CloseHandle((HANDLE)_beginthreadex(nullptr, 0, ThreadFindFile, pTempData, 0, nullptr));HANDLE hThread = (HANDLE)_beginthreadex(nullptr, 0, ThreadFindFile, pTempData, 0, nullptr);if (hThread != NULL){    CloseHandle(hThread);}else{    printf("创建线程失败,错误值为:%d\r\n", errno);}

上面是代码修改的部分,当再次执行这个程序的时候,会打印出下面的结果:
这里写图片描述
错误码是12,当查询msdn的时候,会告诉我们这是没有足够内存的错误!!!
这里写图片描述
这里写图片描述

这个错误我们肯定是能够解决的,比如,通过一个循环,直到线程创建成功的时候才退出循环。不过我们不再对这个多线程版本进行修改了,因为从时间上来看,还不如单线程的递归方式扫描的快,所以再进行改进也就没什么意义!!!

原创粉丝点击