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

背景

由于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的时候,会告诉我们这是没有足够内存的错误!!!
这里写图片描述
这里写图片描述

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值