在内存中使用多线程查找数据

前言

  将文件加载到内存中,根据线程数量分块计算,每个线程开始查找.任务分解为:
  1. 先获取文件的行数
  2. 根据行数分配内存大小
  3. 将文件以行的方式加载到内存中
  4. 通过计算,给每个线程进行分块    
  5. 将查找的字符串进行输出  

1.获取文件的行数

//根据文件地址,获取文件的行数
int getFileLine(char *path)								
{
	FILE *pReader = fopen(path, "rb");
	if (pReader == NULL)
	{
		return -1;
	}
	else
	{
		int lineCount = 0;
		while (!feof(pReader))
		{
			char tmpStr[512] = { 0 };
			fgets(tmpStr, 512, pReader);
			lineCount++;
		}
		fclose(pReader);
		return lineCount;
	}
}

2.内存分配和加载文件到内存合为一个方法

//将文件加载到内存中
void loadFileInMemory(char *path)						
{
	if (path == NULL)
	{
		printf("文件地址不能为空!\n");
		return;
	}

	p_Info = calloc(LINES, sizeof(char *));				//进行分配内存并初始化

	FILE *pReader = fopen(path, "rb");
	if (pReader == NULL)
	{
		printf("无法打开文件\n");
		return;
	}
	for (int i = 0; i < LINES; i++)					//知道行数
	{
		char tmpStr[512] = { 0 };
		fgets(tmpStr, 512, pReader);				//从文件流读取到字符串中		
		p_Info[i] = calloc(512, sizeof(char));			//根据字符串的长度进行分配内存
		char *p = strcpy(p_Info[i], tmpStr);			//将字符串拷贝到内存中
		if (p == NULL)
		{
			printf("第%d行,文本内容 %s 出现问题!\n", i, tmpStr);
			break;
		}
	}
	fclose(pReader);
}

3.计算每个线程的起始地址,进行分块

//通过线程数量,计算每个线程执行的起始地址
void splitSegment(char *str)										
{
	int threadCount = 4;							//这里先写死线程数量
	hd = calloc(threadCount, sizeof(HANDLE));				//将线程句柄集合初始化内存

	pQueryInfo = calloc(threadCount, sizeof(struct queryInfo));		//根据线程数量分配多个queryInfo

	int lines = LINES;
	int mod = lines%threadCount;						//根据行数对线程数进行求余

	if (mod == 0)								//1.处理能整除的情况
	{
		for (int i = 0; i < threadCount; i++)
		{
			pQueryInfo[i].id = i;
			pQueryInfo[i].findStr = str;
			pQueryInfo[i].pStart = p_Info + i*(lines / threadCount);
			pQueryInfo[i].len = lines / threadCount;
			hd[i] = _beginthread(searchStr, 0, &pQueryInfo[i]);
		}
	}
	else									//2.处理不能整除的情况
	{
		//让线程数理减1整除,最后1个线程执行剩下的
		int execCount = threadCount - 1;
		for (int i = 0; i < execCount; i++)
		{
			pQueryInfo[i].id = i;
			pQueryInfo[i].findStr = str;
			pQueryInfo[i].pStart = p_Info + i*(lines / execCount);
			pQueryInfo[i].len = lines / execCount;
			hd[i] = _beginthread(searchStr, 0, &pQueryInfo[i]);
		}
		//最后线程,执行求余剩下的部分
		{
			int i = execCount;
			pQueryInfo[i].id = i;
			pQueryInfo[i].findStr = str;
			pQueryInfo[i].pStart = p_Info + i*(lines / threadCount);
			pQueryInfo[i].len = lines % execCount;			//这里是求余剩下的数量	
			hd[i] = _beginthread(searchStr, 0, &pQueryInfo[i]);
		}
	}
}

4.查找字符串

//根据字符串进行查找
void  searchStr(void *pInfo)							
{
	if (pInfo == NULL)
	{
		printf("查找数据不能为空!\n");
		return;
	}
	struct queryInfo *pQueryInfo = pInfo;
	for (int i = 0; i < pQueryInfo->len; i++)
	{
		if (pQueryInfo->pStart[i] != NULL)
		{
			char *pInfo = strstr(pQueryInfo->pStart[i], pQueryInfo->findStr);
			if (pInfo != NULL)
			{
				printf("找到 %s\n", pQueryInfo->pStart[i]);
			}
		}
	}
}

5.测试代码

#define LINES 1007579

struct queryInfo
{
	int *pStart;								//起始地址
	char *findStr;								//查找的字符串
	int len;								//文件的行数
	int id;									//线程id
};

struct queryInfo *pQueryInfo;							//存放查询信息

HANDLE *hd;									//存放线程句柄集合
char **p_Info;									//存放文件每一行的地址

//释放文件加载到内存区域的这一部分内存
void freeMemory()
{
	if (p_Info != NULL)
	{
		for (int i = 0; i < LINES; i++)
		{
			if (p_Info[i] != NULL)
			{
				free(p_Info[i]);
			}
		}
		p_Info = NULL;
	}
}

int main(int argc, char *argv[])
{
	char filePath[64] = "G:\\bigdata\\kf\\1_1.txt";
	//int lineCount = getFileLine(filePath);		//只使用一次
	//printf("lineCount=%d\n", lineCount);

	loadFileInMemory(filePath);
	while (1)
	{
		char tmpStr[64];
		scanf("%s", tmpStr);
		if (strcmp("exit", tmpStr) == 0)               //输入exit的时候,释放内存,终止循环
		{
			freeMemory();
			break;
		}
		splitSegment(tmpStr);
	}
	system("pause");
	return 0;
}

结语

  现在实现的方式,都是简单粗暴的,要是大文件,上几个g的文本,8g内存是不够用的,所以采用的都是讲大文件分割成小文件,在加载到内存上进行处理的.后面会建立索引和二分查找,进行文本的快速查找.
秋风 2016-07-17