文件扫描器(一):
正常单线程文件搜索:
1:在Windows.h里面,有FindFirstFile和FindNextFile两个函数,可以用于查找一个文件夹下面所有的文件,如下:
HANDLE WINAPI FindFirstFile(
_In_ LPCTSTR lpFileName, //路径名称
_Out_ LPWIN32_FIND_DATA lpFindFileData //他将会由方法填充,是一个结构体
);
1:WIN32_FIND_DATA:
typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes; //文件的属性,是多重属性(文件为多重属性,按位表示),是组合值,包含readonly等,
FILETIME ftCreationTime; //创建的时间,在看文件属性的时候,就会有这些信息,还有修改时间等等
FILETIME ftLastAccessTime; //最后一次访问时间
FILETIME ftLastWriteTime; //最后一次修改时间
DWORD nFileSizeHigh; //文件大小,一个DWORD只能表示4GB,因此分了高低位
DWORD nFileSizeLow; //文件大小低位
DWORD dwReserved0; //保留位
DWORD dwReserved1; //保留位
TCHAR cFileName[MAX_PATH]; //重要参数,FindFirstFile会在指定的文件夹里面挨个遍历,每次找到都会改变这个结构体
TCHAR cAlternateFileName[14]; //文件后缀名,例如.txt
} WIN32_FIND_DATA, *PWIN32_FIND_DATA, *LPWIN32_FIND_DATA;
2:FindFirstFile将返回用于设置FindNextFile第一个参数的一盒HANDLE。
3:文件属性不光光是文件,也有文件夹(有隐藏等属性),例如:0x11代表一个只读的文件夹,其中0x10代表文件夹,0x1代表只读。多重属性是按或运算组合在一起的,他不会改变里面其他的属性值,在属性判断的时候,也禁止用==来判断,而应该取对应位在判断
4:文件夹的所有属性都在这个里面: https://msdn.microsoft.com/en-us/library/windows/desktop/gg258117(v=vs.85).aspx。
5:一般,和之前提到的ToolHelper一样,都是先FindFirstFile,然后用dowhile循环不断的调用FindNextFile。FindNextName会一直查找,直到把这个文件夹的所有文件查找一边。查找过程中,他会不断的更改我们的FIND_DATA结构体,可以用vector保存起来
6:特别注意:FindFirstFile传入的第一个参数一定要是一个完整的文件夹的名称的搜索条件,直接传入路径是不可以的,可以像下面这些例子这样传递:
eg:L"C:\\*.exe" L"D:\\Picture\\*.jpeg"
BOOL WINAPI FindNextFile(
_In_ HANDLE hFindFile,
_Out_ LPWIN32_FIND_DATA lpFindFileData
);
7:FindNextFile第一个参数,就是FindFirstFile返回的句柄,第二个参数就是传入的结构体。
2:以下代码是查找固定目录下符合条件文件(文件名中包含关键字,符合要找的文件后缀)的例子:
#include "stdafx.h"
#include <iostream>
#include <windows.h>
#include <string>
#include <tchar.h>
//#include <ctime>
int g_nFindedFileNum = 0; //已找到文件个数
int g_nSearchFileNum = 0; //找过的文件
int g_nSearchDirNum = 0; //找过的文件夹
std::wstring MakeStandarDirName(const std::wstring &wstrDirName)
{
if (wstrDirName.back() != '\\')
return wstrDirName + L"\\";
else
return wstrDirName;
}
void MyFileFind(std::wstring &wstrBeginDirName, std::wstring &wstrSearch, std::wstring wstrFilte = L"*.*")
{
WIN32_FIND_DATAW fileFind = { 0 };
HANDLE hFileFind = FindFirstFileW((MakeStandarDirName(wstrBeginDirName)+wstrFilte).c_str(), &fileFind);
if (INVALID_HANDLE_VALUE != hFileFind)
{
do
{
if ((wcscmp(fileFind.cFileName, L".") == 0) || (wcscmp(fileFind.cFileName, L"..") == 0))
continue;
//如果是文件夹,就应该再次向下查找,如果是文件,就应该对比是否符合查找文件的条件,而不应该直接就打印出名字。
//_tprintf(L"FileName:\t%s", fileFind.cFileName);
if (fileFind.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
{
//_tprintf(L"\t这是一个文件夹\r\n");
std::wstring wstrPath = MakeStandarDirName(wstrBeginDirName) + fileFind.cFileName;
MyFileFind(wstrPath, wstrSearch);
++g_nSearchDirNum;
}
else
{
//_tprintf(L"\t这是一个文件\r\n");
if (wcsstr(fileFind.cFileName, wstrSearch.c_str()) != nullptr) //文件名中存在相同名段
{
++g_nFindedFileNum;
_tprintf(L"FileName:\t%s\r\n", (MakeStandarDirName(wstrBeginDirName)+fileFind.cFileName).c_str());
}
++g_nSearchFileNum;
}
} while (FindNextFileW(hFileFind, &fileFind));
}
}
int _tmain(int argc, _TCHAR* argv[])
{
setlocale(LC_ALL, "chs");
std::wstring wstrBeginDirName = L"C:\\";
std::wstring wstrSearchName = L"ntdll";
DWORD dwBeginTime = GetTickCount();
//clock_t tb = clock(); //多种计时方式
MyFileFind(wstrBeginDirName, wstrSearchName);
//tb = clock() - tb;
DWORD dwTime = GetTickCount()-dwBeginTime;
std::cout << "搜索用时" << dwTime << "ms" /*<< tb << "ms" */<< std::endl;
std::cout << "找到的文件个数" << g_nFindedFileNum << std::endl;
std::cout << "找过的文件个数" << g_nSearchFileNum << std::endl;
std::cout << "找过的文件夹个数" << g_nSearchDirNum << std::endl;
return 0;
}
1:以上代码,能够找到C盘下任意文件名带有ntdll的文件,并将其目录打印出来,最后,打印出找到的文件个数,找过的文件个数,找过的文件夹个数。
2:整个搜索的过程是通过递归的方式来实现的,并且,只使用了单线程来完成。在我的测试中:输出数据如下:
搜索用时11653ms
找到的文件个数88
找过的文件个数185176
找过的文件夹个数58574
3:经过实际测试,这个数据相对比Windows自带的搜索会快一些。
多线程文件搜索:
1:多线程搜索思路:
1:一种方式是单线程找到文件夹,而非文件的时候,就创建一个线程,每个文件夹对应一个线程,非常多的线程进行每个文件夹的搜索。按这个的思路,程序就会按如下进行:
1:主线程找到C盘下的很多个文件夹和文件。
2:为每个文件夹分配一个线程,进行下一级搜索,并对已经搜索的文件进行判断。
3:每个线程继续向下找,找到文件就判断,找到文件夹就创建线程,依次进行下去。
注意:这样,按我的电脑来说,除了主线程,总共还会创建58574个子线程,实际上,效率很可能得不到提高,反而会降低,具体原因后面再说。
2:多线程搜索分析:
1:一种方式是单线程找到文件夹,而非文件的时候,就创建一个线程,每个文件夹对应一个线程,非常多的线程进行每个文件夹的搜索。按这个的思路,程序就会按如下进行:
1:主线程找到C盘下的很多个文件夹和文件。
2:为每个文件夹分配一个线程,进行下一级搜索,并对已经搜索的文件进行判断。
3:每个线程继续向下找,找到文件就判断,找到文件夹就创建线程,依次进行下去。
注意:这样,按我的电脑来说,除了主线程,总共还会创建58574个子线程,实际上,效率很可能得不到提高,反而会降低,具体原因后面再说。
2:多线程搜索分析:
1:首先,我们需要管理非常多的线程,需要用一个vector将所有线程的句柄都保存起来,必须要等所有线程都结束的时候,才能算是搜索完成,因此,我们需要WaitForMultipleObjects()来等待所有线程推出。
2:在WaitForMultipleObjects()的时候,它只会等待主线程里面创建的第一个线程结束,或者是这个线程结束前,创建了的线程和这个线程一起,如果刚刚好这些都结束,那么,主线程就会执行到程序退出地方的return 0;,紧接着调用vector的析构函数,最后,导致创建的其他线程在创建线程的时候,vector的push_back函数出错。因此,这里还是必须要使用事件的方式来做,wait只能wait这个事件内和对象
3:我们并不知道最后有多少个线程,不知道有多少层文件夹,也不能确定我们要wait的线程有多少个,因此,可以使用一个计数,还在工作的线程的计数,当这个计数为0的时候,就代表所有线程都已经退出了。
4:通过计数的方式不得不考虑一个问题,那就是线程是抢占式的执行的,如果遇到一个线程还没抢到事件启动,很多线程都结束的情况,那么就可能导致这个计数被提前设置为0。因此,要避免这种情况。
5:可以通过在_beginthreadex();的前面就将计数加一,而不应该在线程的回调函数里面最开始的地方去加一,这样就可以解决这个问题。
3:具体代码实现,本代码属于未完工代码,因此,在运行过程中可能会出现问题,也可能不会出现问题:
#include "stdafx.h"
#include <iostream>
#include <windows.h>
#include <string>
#include <tchar.h>
//#include <ctime>
#include <process.h>
#include <vector>
long g_lFindedFileNum = 0; //已找到文件个数
long g_lSearchFileNum = 0; //找过的文件
long g_lSearchDirNum = 0; //找过的文件夹
HANDLE g_hExitEvent; //所有线程退出完毕事件
std::vector<HANDLE> g_hThread; //保存所有的线程
long g_lWorkThreadNum = 0; //在工作的线程数
CRITICAL_SECTION g_cs;
std::wstring MakeStandarDirName(const std::wstring &wstrDirName)
{
if (wstrDirName.back() != '\\')
return wstrDirName + L"\\";
else
return wstrDirName;
}
void MyFileFind(std::wstring &wstrBeginDirName, std::wstring &wstrSearch, std::wstring wstrFilte = L"*.*")
{
WIN32_FIND_DATAW fileFind = { 0 };
HANDLE hFileFind = FindFirstFileW((MakeStandarDirName(wstrBeginDirName)+wstrFilte).c_str(), &fileFind);
if (INVALID_HANDLE_VALUE != hFileFind)
{
do
{
if ((wcscmp(fileFind.cFileName, L".") == 0) || (wcscmp(fileFind.cFileName, L"..") == 0))
continue;
//如果是文件夹,就应该再次向下查找,如果是文件,就应该对比是否符合查找文件的条件,而不应该直接就打印出名字。
//_tprintf(L"FileName:\t%s", fileFind.cFileName);
if (fileFind.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
{
//_tprintf(L"\t这是一个文件夹\r\n");
std::wstring wstrPath = MakeStandarDirName(wstrBeginDirName) + fileFind.cFileName;
MyFileFind(wstrPath, wstrSearch);
++g_lSearchDirNum;
}
else
{
//_tprintf(L"\t这是一个文件\r\n");
if (wcsstr(fileFind.cFileName, wstrSearch.c_str()) != nullptr) //文件名中存在相同名段
{
++g_lFindedFileNum;
//_tprintf(L"FileName:\t%s\r\n", (MakeStandarDirName(wstrBeginDirName)+fileFind.cFileName).c_str());
}
++g_lSearchFileNum;
}
} while (FindNextFileW(hFileFind, &fileFind));
}
}
typedef struct tagThreadDate //创建线程时传入的指定文件搜索属性的结构体变量
{
std::wstring wstrDirName;
std::wstring wstrSearch;
std::wstring wstrFilte;
}ThreadDate;
unsigned __stdcall ThreadFileFind(void *lParam)
{
InterlockedAdd(&g_lWorkThreadNum, 1);
ThreadDate *pData = (ThreadDate*)lParam;
WIN32_FIND_DATAW fileFind = { 0 };
HANDLE hFileFind = FindFirstFileW((MakeStandarDirName(pData->wstrDirName) + pData->wstrFilte).c_str(), &fileFind);
if (INVALID_HANDLE_VALUE != hFileFind)
{
do
{
if (wcscmp(fileFind.cFileName, L".") == 0 || wcscmp(fileFind.cFileName, L"..") == 0)
continue;
if (fileFind.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
{
ThreadDate *pTempData = new ThreadDate;
pTempData->wstrDirName = MakeStandarDirName(pData->wstrDirName) + fileFind.cFileName;
pTempData->wstrFilte = pData->wstrFilte;
pTempData->wstrSearch = pData->wstrSearch;
EnterCriticalSection(&g_cs);
g_hThread.push_back((HANDLE)_beginthreadex(nullptr, 0, ThreadFileFind, pTempData, 0, nullptr));
LeaveCriticalSection(&g_cs);
InterlockedAdd(&g_lSearchDirNum, 1);
}
else
{
//_tprintf(L"\t这是一个文件\r\n");
if (wcsstr(fileFind.cFileName, pData->wstrSearch.c_str()) != nullptr) //文件名中存在相同名段
{
//_tprintf(L"FileName:\t%s\r\n", (MakeStandarDirName(pData->wstrDirName) + fileFind.cFileName).c_str());
InterlockedAdd(&g_lFindedFileNum, 1);
}
InterlockedAdd(&g_lSearchFileNum, 1);
}
} while (FindNextFileW(hFileFind, &fileFind));
}
delete pData;
InterlockedAdd(&g_lWorkThreadNum, -1);
if (g_lWorkThreadNum == 0)
SetEvent(g_hExitEvent);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
setlocale(LC_ALL, "chs");
//多线程搜索
InitializeCriticalSection(&g_cs);
g_hExitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
ThreadDate *pData = new ThreadDate;
pData->wstrDirName = L"C:\\";
pData->wstrSearch = L"ntdll";
pData->wstrFilte = L"*.*";
DWORD dwTime = GetTickCount();
g_hThread.push_back((HANDLE)_beginthreadex(nullptr, 0, ThreadFileFind, pData, 0, nullptr)); //这个地方肯定是单线程,就没有必要用关键段锁上了
// WaitForMultipleObjects(g_hThread.size(), g_hThread.data(), TRUE, INFINITE);
WaitForSingleObject(g_hExitEvent, INFINITE);
//这里只会等待上面这个thread,这个结束了,就退出了,然后程序最后return的时候,已近调用了vector的西沟函数,所以,vector里面会报异常
dwTime = GetTickCount() - dwTime;
for each (auto pThread in g_hThread)
{
CloseHandle(pThread);
}
std::cout << "多线程搜索用时" << dwTime << "ms" /*<< tb << "ms" */<< std::endl;
std::cout << "找到的文件个数" << g_lFindedFileNum << std::endl;
std::cout << "找过的文件个数" << g_lSearchFileNum << std::endl;
std::cout << "找过的文件夹个数" << g_lSearchDirNum << std::endl;
DeleteCriticalSection(&g_cs);
std::cout << std::endl;
//递归搜索
g_lFindedFileNum = 0; //已找到文件个数
g_lSearchFileNum = 0; //找过的文件
g_lSearchDirNum = 0; //找过的文件夹
std::wstring wstrBeginDirName = L"C:\\";
std::wstring wstrSearchName = L"ntdll";
DWORD dwBeginTime = GetTickCount();
//clock_t tb = clock(); //多种计时方式
MyFileFind(wstrBeginDirName, wstrSearchName);
//tb = clock() - tb;
dwBeginTime = GetTickCount() - dwBeginTime;
std::cout << "递归搜索用时" << dwBeginTime << "ms" /*<< tb << "ms" */ << std::endl;
std::cout << "找到的文件个数" << g_lFindedFileNum << std::endl;
std::cout << "找过的文件个数" << g_lSearchFileNum << std::endl;
std::cout << "找过的文件夹个数" << g_lSearchDirNum << std::endl;
return 0;
}
文件扫描器(二):
1:线程调试注意事项:
1:在多线程的调试中,除了要特别注意线程的同步之外,还需要注意的是, 可以使用线程窗口查看有多少线程,线程窗口在程序运行的时候,调试->窗口->线程。程序暂停的时候,可以查看每个线程运行在什么地方。
2:在多线程调试的时候,可能会遇到程序卡死的情况,在之前的思路做的文件扫描器里面, 在短时间内创建了上千上万个线程,很可能会导致程序卡死不动。
3:线程与线程之间可能会导致的问题,并不是说线程有上百条之后才会出现,只要不只一个线程,都是有可能出现的,一般的逻辑是:如果十来条线程,一直没有出问题,那么同样的几百条也是不会出问题的。
4:在进行线程调试的时候,主要是观察当前运行的哪一条线程,至于线程里面做的事,应该是了如指掌的。可以在线程回调函数开始的地方下断点。
5:现成的调试,并不能只依靠断点来判断问题所在,而问题很可能是其他地方或者其他线程里面出的问题。其他县城是否改变这些公用的数据。多线程调试的难点在于无法判断其他县城是否改变这些共用的数据,从而导致程序出错。
2:线程调试举例
例如,在之前的问题中,主线程启动的线程,启动后,会将我们设置的线程启动个数计数加一,在这里面,有多少个文件夹,就会再次启动多少个线程,但是, 在主线程启动的第一个线程结束的时候,启动的其他线程并不一定都抢到了时间片,如果这些线程都没有抢到,那么在这个线程退出的时候,会将线程个数减一,这时,就会将事件内核对象设置为有信号,导致,主线程提前结束,析构vector等,最后导致程序出错,或者结果出错提前退出。
因此,在多线程的程序里面,非常不建议短时间启动大量线程。而是等一些线程执行完在创建线程。
3:线程的调试方法:
1:在同时启动的线程不太多的情况下,可以单步调试,看线程什么时候启动,什么时候怎么运行,一般情况下, 多线程的错误都是来源于线程抢占式执行带来的,只要找到问题来源,直到多线程怎么编写会有一些怎样的坏处。养成好的习惯,有了经验,就行了。
2:多线程调试的时候,一定要给每个线程改名,这样才知道是那个线程。
3:在主线程里面,可以双击其他线程,转到其他线程去运行,这样可以观察多线程在执行的情况下,每个值的变化规律。
1:在多线程的调试中,除了要特别注意线程的同步之外,还需要注意的是, 可以使用线程窗口查看有多少线程,线程窗口在程序运行的时候,调试->窗口->线程。程序暂停的时候,可以查看每个线程运行在什么地方。
2:在多线程调试的时候,可能会遇到程序卡死的情况,在之前的思路做的文件扫描器里面, 在短时间内创建了上千上万个线程,很可能会导致程序卡死不动。
3:线程与线程之间可能会导致的问题,并不是说线程有上百条之后才会出现,只要不只一个线程,都是有可能出现的,一般的逻辑是:如果十来条线程,一直没有出问题,那么同样的几百条也是不会出问题的。
4:在进行线程调试的时候,主要是观察当前运行的哪一条线程,至于线程里面做的事,应该是了如指掌的。可以在线程回调函数开始的地方下断点。
5:现成的调试,并不能只依靠断点来判断问题所在,而问题很可能是其他地方或者其他线程里面出的问题。其他县城是否改变这些公用的数据。多线程调试的难点在于无法判断其他县城是否改变这些共用的数据,从而导致程序出错。
2:线程调试举例
例如,在之前的问题中,主线程启动的线程,启动后,会将我们设置的线程启动个数计数加一,在这里面,有多少个文件夹,就会再次启动多少个线程,但是, 在主线程启动的第一个线程结束的时候,启动的其他线程并不一定都抢到了时间片,如果这些线程都没有抢到,那么在这个线程退出的时候,会将线程个数减一,这时,就会将事件内核对象设置为有信号,导致,主线程提前结束,析构vector等,最后导致程序出错,或者结果出错提前退出。
因此,在多线程的程序里面,非常不建议短时间启动大量线程。而是等一些线程执行完在创建线程。
3:线程的调试方法:
1:在同时启动的线程不太多的情况下,可以单步调试,看线程什么时候启动,什么时候怎么运行,一般情况下, 多线程的错误都是来源于线程抢占式执行带来的,只要找到问题来源,直到多线程怎么编写会有一些怎样的坏处。养成好的习惯,有了经验,就行了。
2:多线程调试的时候,一定要给每个线程改名,这样才知道是那个线程。
3:在主线程里面,可以双击其他线程,转到其他线程去运行,这样可以观察多线程在执行的情况下,每个值的变化规律。