4、海量日志数据,提取出某日访问百度次数最多的那个IP。
回答:
如果日志文件足够的大,大到不能完全加载到内存中的话。
那么可以考虑分而治之的策略,按照IP地址的hash(IP)%1024值,将海量日志存储到1024个小文件中。每个小文件最多包含4M个IP地址。
对于每个小文件,可以构建一个IP作为key,出现次数作为value的hash_map,并记录当前出现次数最多的1个IP地址。
有了1024个小文件中的出现次数最多的IP,我们就可以轻松得到总体上出现次数最多的IP。
以上是题目,笔者对hash并不是很熟悉,所以借此机会能够练习一下哈希函数的作用吧:以上是hackbuteer1大神的解读,我想自己清理一遍希望能够获取一些新的感受:
1.对于海量日志,我们要将起分离开来,怎么分离呢,就是将同一个IP地址的记录分散到同一个日志文件当中去,这就是分离策略,这里面使用的是哈希值的方法
2.读取每一个小文件的最大的IP访问量,将该IP地址记录下来,然后将1024个记录进行比较,获得最大的那个,即是结果所在了
但是问题来了,既然内存不够,那么我们怎么读取文件的一部分呢?答案是使用文件映射,
看下FileMapping的使用建议
File mapping is effective in the following situations:
You have a large file whosecontents you want to access randomly one or more times.
You have a small file whosecontents you want to read into memory all at once and accessfrequently. This technique is best for files that are no more thana few virtual memory pages in size.
You want to cache specificportions of a file in memory. File mapping eliminates the need tocache the data at all, which leaves more room in the system diskcaches for other data.
You should not use file mapping in the followingsituations:
You want to read a filesequentially from start to finish only once.
The file is several hundredmegabytes or more in size. (Mapping large files fills virtualmemory space quickly. In addition, your program may not have theavailable space if it has been running for a while or its memoryspace is fragmented.)
其含义就是
1.如果你有一个大文件,但是只是
随机读取其不同位置的内容,可以使用文件映射
2.你有一个小文件,并且想
快速的将其读入到内存当中,使用该技术是最适宜的,
3.如果你想缓存指定内容,在内存中,文件映射文件映射则可以使得不去专门去做该缓存操作,文件映射为其他的数据在系统中留下了更多的空间
也就是说,使用文件映射,文件在打开或者使用CreateFileMapping时,都没有将文件内容装入内存而是在读取内容的时候才会读取文件内容到内存中去,也就是说对一个很大的文件我们可以Mapping一部分,操作完成后然后Unmapping,再mapping下一部分,..以此类推,我们就可以解决文件过大,而内存不足的问题了首先要通过CreateFile()函数来创建或打开一个文件内核对象,这个对象标识了磁盘上将要用作内存映射文件的文件。在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后,只指定了映像文件的路径,映像的长度还没有指定。为了指定文件映射对象需要多大的物理存储空间还需要通过CreateFileMapping()函数来创建一个文件映射内核对象以告诉系统文件的尺寸以及访问文件的方式。在创建了文件映射对象后,还必须为文件数据保留一个地址空间区域,并把文件数据作为映射到该区域的物理存储器进行提交。由MapViewOfFile()函数负责通过系统的管理而将文件映射对象的全部或部分映射到进程地址空间。此时,对内存映射文件的使用和处理同通常加载到内存中的文件数据的处理方式基本一样,在完成了对内存映射文件的使用时,还要通过一系列的操作完成对其的清除和使用过资源的释放。这部分相对比较简单,可以通过UnmapViewOfFile()完成从进程的地址空间撤消文件数据的映像、通过CloseHandle()关闭前面创建的文件映射对象和文件对象。与之不同的是fstream文件流,该文件在打开时就已经将文件内容读入内存中,故而,如果文件内容过大,则会导致文件打开失败。好了,现在假设每一个IP记录独占一行,所以呢,我们应该怎么做呢,就是要将,IP地址映射到不同的小文件当中去,源代码如下
#include "stdafx.h"
#include<iostream>
#include<fstream>
#include<Windows.h>
using namespace std;
char* strURL[200];
bool isEnd=false;
//根据URL值,获得该URL的哈希值,分配到不同的小文件当中
int getHash(char*URL)
{
int index=0;
int nCount=0;
while(URL[index]!='\0')
{
nCount+=URL[index];
index++;
}
return nCount%1024;
}
//根据文件句柄获和文件偏移量获得当前位置的下一个URL
//该函数主要是根据文件当前的位置,开始一个一个读取字符,知道遇到换行符,即可说明,该URL已经结束,返回该URL的内容即可
//其实就是自己实现.getLine函数
char* getSingleURL(LONG offset,LONG &curPos)
{
char* strFilePath="D:\\out.txt";
HANDLE hFile=CreateFileA(strFilePath,FILE_ALL_ACCESS,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
cout<<"文件打开失败:"<<GetLastError();
return 0;
}
int nFileSize=GetFileSize(hFile,NULL);
curPos=offset;
//首先移动文件指针
LONG midVal=0;
DWORD nowPos=SetFilePointer(hFile,offset,&midVal,FILE_BEGIN);
if(nowPos>nFileSize)
{
isEnd=true;
return NULL;
}
char tmp=1;
DWORD ByteRead=0;
while(tmp!='\n')
{
if(ReadFile(hFile,&tmp,1,&ByteRead,NULL))
{
if(ByteRead!=1)
{
isEnd=true;
break;
}
else
offset++;
}
else
{
cout<<GetLastError()<<endl;
}
}
DWORD urlLen=offset-curPos;
if(urlLen==0)
{
cout<<"文件读取结束\n";
return NULL;
}
char* curURL=new char[urlLen+1];
memset(curURL,0,urlLen+1);
SetFilePointer(hFile,curPos,&midVal,FILE_BEGIN);
ReadFile(hFile,curURL,urlLen,&ByteRead,NULL);
curPos=offset;
CloseHandle(hFile);
return curURL;
}
//将URL写入对应的文件当中去
bool WriteToFile(char* URL,int nFileMark)
{
ofstream out;
char tmpName[20]={0};
sprintf(tmpName,"%s%d.txt","D:\\result",nFileMark);
out.open(tmpName,ios::app);
if(URL[strlen(URL)-1]=='\n')
{
URL[strlen(URL)-1]='\0';
}
out.write(URL,strlen(URL));
return true;
}
int main()
{
//读取文件中的URL值
LONG nOffset=0,nCurPos=0;
while(true)
{
char* curUrl=getSingleURL(nOffset,nCurPos);
if(isEnd)
{
break;
}
WriteToFile(curUrl,getHash(curUrl));
delete[]curUrl;
nOffset=nCurPos;
}
return 0;
}
PS:笔者在调试这程序的时候本没什么问题,但是在SetFilePointer总是返回0,花费了我整整一天的时间,结果是参数填写错误!!还是希望以后的初学者慎重啊...