20170816WindowsPrj01_01_文件扫描器

文件扫描器(一):

正常单线程文件搜索:


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:首先,我们需要管理非常多的线程,需要用一个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:在主线程里面,可以双击其他线程,转到其他线程去运行,这样可以观察多线程在执行的情况下,每个值的变化规律。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值