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的时候,会告诉我们这是没有足够内存的错误!!!
这个错误我们肯定是能够解决的,比如,通过一个循环,直到线程创建成功的时候才退出循环。不过我们不再对这个多线程版本进行修改了,因为从时间上来看,还不如单线程的递归方式扫描的快,所以再进行改进也就没什么意义!!!
- Windows Practice_文件搜索器(一)递归
- Windows Practice_文件搜索器(二)_多线程调试
- Windows Practice_文件搜索器(三)_线程池
- Windows Practice_文件_文件分割器(一)
- Windows Practice_文件搜索器(四)_封装文件扫描器
- Windows Practice_文件_内存映射(一)
- Windows Practice_文件_文件分割器(二)
- Windows Practice_文件_文件分割器(三)
- Windows Practice_闹钟(一)_简易记事本
- Windows Practice_文件_文件基础操作
- Windows Practice_文件_注册表操作
- Windows Practice_闹钟(二)_简易记事本
- Windows Practice_闹钟(四)_GDI对象概述
- Windows Practice_闹钟(五)_GDI绘制工具
- Windows Practice_闹钟(六)_控件对象
- 文件搜索器的实现(一)
- 递归方式搜索文件
- 递归搜索文件
- python字符串、列表、字典相互转换
- iOS
- Android学习之网络请求(retrofit)
- Android之窗口浅析
- POJ 1062 昂贵的聘礼(SPFA)
- Windows Practice_文件搜索器(一)递归
- 学习SpringMVC笔记——Intellij IDEA创建SpringMVC项目
- CodeForces 35 D.Animals(贪心)
- redis必杀命令:集合(Set)
- Android的View动画
- Android的各种通知Notification、Dialog、Toast、Snackbar
- Android使用Fragment仿微信底部导航栏
- Android开机启动
- Android在ImageView上直接显示网络图片