解题笔记(9)——提取某日访问次数最多的那个IP

        问题描述:从海量数据日志中,提取出某日访问次数最多的那个IP。

        思路:对于海量数据的处理,主要采取的策略就是分而治之,即缩减问题的规模,将一个大的问题划分成若干等价的小问题。然后解决这些小问题,最后将获得的小问题解综合起来,得出原问题的解。用到比较多的技术主要有散列、位图、堆、trie树、mapreduce、K路归并(败者树)等。其中散列用的尤为多。

         对于本问题,假定某日访问的IP地址已经从数据日志中提取出来,存放在一个大的二进制文件中。下面的工作主要是找目标IP——文件中出现次数最多的那个IP。这个文件很大,内存无法完全放下,内排序的方法行不通。可以采取如下措施:

        (1)利用散列函数,将大文件中的IP地址散列到若干个文件中。相同的IP地址肯定在同一个文件中。

        (2)处理每个小文件,找到该文件中出现次数最多的那个IP,记录下IP地址和出现次数。可以用hash_map,IP地址为键值、出现次数为数值。

        (3)将第(2)步中找到的IP地址及出现次数综合起来,找到这些IP地址中出现次数最多的那个IP。

         简单实现:接下来给出一种简单的实现,效率比较低。测试中,从一个含4亿个IP地址的文件中提取目标IP,一共用了52分钟。其中大量的时间用于文件的读写,约为30分钟。另外有7分钟用于产生含4亿个随机数的文件。真正用于计算的时间为15分钟。由于C++标准STL中没有hash_map,因此该用map实现第(2)步,如果改用hash_map,应该能减少部分计算的时间。

        另外,如果设置读写缓冲区,经过测试,缓冲区为128字节时,读写文件的时间从原来的30分钟减为25分钟左右。进一步增大缓冲区大小,提升的速度比非常小,待求解。这里设置缓冲区不是指这种方式:

char buffer[1024]; 

streambuf * ptrbuf = outFile.rdbuf(); 

 ptrbuf-> pubsetbuf(buffer,1024); 

而是定义一个整形数组,每次读写时,读写一块数据而不是一个整数。

单个读写   outFile.write((char*)&x,sizeof(unsigned));

块读写       outFile.write((char *)buffer,BUFFER_SIZE*sizeof(unsigned));

VC6.0下编译运行通过

#pragma warning(disable:4786) //VC6.0中 忽略警告
#include <fstream>
#include <iostream>
#include <map>
#include <string>
#include <ctime>
using namespace std;

const unsigned N=400000000;      //随机产生的IP地址数
const unsigned FILE_NUM=16;      //产生的小文件个数
const unsigned HASH_SHIFT=28;    //散列值的位移量

inline unsigned HashInt(unsigned value); //将整数散列到0到FILE_NUM之间
bool ProduceIP(string fileName);                //随机产生IP地址,看成是32位无符号数
bool DecomposeFile(string fileName);     //分而治之,将大文件分为若干个小文件
bool FindTargetIP(unsigned result[2]);      //找到出现次数最多的IP

int main()
{
	unsigned start,end;      //记录总的运行时间
	unsigned start1,end1;  //产生大文件的时间
	unsigned start2,end2;  //分解大文件的时间
	unsigned start3,end3;  //找出现IP次数最多的时间

	string name="IP.bin";       //大文件
	unsigned result[2]={0,0};   //保存结果

	start=clock();
	start1=clock();
	//随机产生大量IP
	if(ProduceIP(name)==false)
		return 1;
	end1=clock();

	start2=clock();
	//分而治之
	if(DecomposeFile(name)==false)
		return 1;
	end2=clock();

	start3=clock();
	//找到出现次数最多的IP
	if(FindTargetIP(result)==false)
		return 1;
	end3=clock();
	end=clock();

	//打印结果
	cout<<"total run time : "<<(end-start)/1000.0<<endl;
	cout<<"ProduceIP() run time : "<<(end1-start1)/1000.0<<endl;
	cout<<"DecomposeFile() run time : "<<(end2-start2)/1000.0<<endl;
	cout<<"FindTargetIP() run time : "<<(end3-start3)/1000.0<<endl;
	cout<<"IP : "<<(result[0]>>24)<<'.'<<((result[0]&0x00ff0000)>>16)<<'.';
	cout<<((result[0]&0x0000ff00)>>8)<<'.'<<((result[0]&0x000000ff))<<endl;
	cout<<"appear time : "<<result[1]<<endl;
	return 0;
}
//将整数散列到0到FILE_NUM之间
inline unsigned HashInt(unsigned value)
{
	//斐波那契(Fibonacci)散列法 hash_key=(value * M) >> S;
	//value是16位整数,M = 40503 
	//value是32位整数,M = 2654435769 
	//value是64位整数,M = 11400714819323198485
	//S与桶的个数有数,如果桶的个数为16,那么S为28
	//对于32位整数,S=32-log2(桶的个数)
	return (value*2654435769)>>HASH_SHIFT; 
}
//随机产生IP地址 看成是32位无符号数
bool ProduceIP(string fileName)
{
	ofstream outFile(fileName.c_str(),ios::binary);
	if(!outFile)
	{
		cerr<<"error: unable to open output file : "<<fileName<<endl;
		return false;
	}
	srand(time(0));
	for(unsigned i=0;i<N;i++)
	{
		//产生一个大整数用来模拟IP地址
		unsigned x=((rand()%256)<<24)|((rand()%256)<<16)|((rand()%256)<<8)|(rand()%256);
		outFile.write((char*)&x,sizeof(unsigned));
	}
	return true;
}
//分而治之 将大文件分为若干个小文件
bool DecomposeFile(string fileName)
{
	ofstream outFiles[FILE_NUM];
	int i;
	for(i=0;i<FILE_NUM;i++)
	{
		//小文件的名称
		char buffer[10];
		string name="tmp";
		itoa(i,buffer,10);
		name=name+buffer+".bin";
		//打开小文件
		outFiles[i].open(name.c_str(),ios::binary);
		if(!outFiles[i])
		{
			cerr<<"error: unable to open output file :"<<name<<endl;
			return false;
		}
	}
	ifstream inFile(fileName.c_str(),ios::binary);
	while(inFile.good()) 
	{
		//散列到各个小文件中
		unsigned int value=0;
		if(inFile.read((char*)&value,sizeof(unsigned)))
		{
			outFiles[HashInt(value)].write((char*)&value,sizeof(unsigned));
		}
	}
	//关闭文件
	inFile.close();
	for(i=0;i<FILE_NUM;i++)
		outFiles[i].close();
	return true;
}
//找到出现次数最多的IP
bool FindTargetIP(unsigned result[2])
{
	result[0]=0;
	result[1]=0;
	for(int i=0;i<FILE_NUM;i++)
	{
		char buffer[10];
		string name="tmp";
		itoa(i,buffer,10);
		name=name+buffer+".bin";
		//处理每个小文件
		ifstream inFile;
		inFile.open(name.c_str(),ios::binary);
		if(!inFile)
		{
			cerr<<"error: unable to open input file :"<<name<<endl;
			return false;
		}
		//核心代码,由于STL中没有hash_map,用map来代替
		map<unsigned,unsigned> ip_count;
		while(inFile.good())
		{
			unsigned key=0;
			if(inFile.read((char*)&key,sizeof(unsigned)))
			{
				ip_count[key]++;
			}
		}
		map<unsigned,unsigned>::iterator it=ip_count.begin();
		for(;it!=ip_count.end();it++)
		{
			if(it->second>result[1])
			{
				result[0]=it->first;
				result[1]=it->second;
			}
		}
		inFile.close();
	}
	return true;
}


         本人享有博客文章的版权,转载请标明出处 http://blog.csdn.net/wuzhekai1985

你可能感兴趣的:(解题笔记(9)——提取某日访问次数最多的那个IP)