今天看见一个腾讯笔试题,正好研究了一下大内存操作和文件映射等问题。
题目是:一个文件中有40亿个整数,每个整数为四个字节,内存为1GB,写出一个算法:求出这个文件里的整数里不包含的一个整数
算法一:分配512MB内存,每一bit代表一个整数,测试程序(VC6)如下:
//一个bit代表一个数,实际上只需要4096/8=512兆的内存空间(即(4096/32)*1024*1024个整数数组)
//unsigned long p[(4096/32)*1024*1024];//栈默认不允许分配这么大的空间!!
bool bFound = false;
unsigned long *p,lResult;
p = new unsigned long[(4096/32)*1024*1024];//这里堆分配这么大会较慢,约20秒,但可以完成
//初始化内存,如果内存小,这句会很慢
memset(p,0,(4096/8)*1024*1024);
//直接读文件的办法:分段读入文件内容,把读入的内容4个字节一组当无符号整数处理
//注意文件中的整数可能会重复,并且没有顺序排列
/*FILE* stream = fopen("c://SkinPPDemo-VC.msi", "r");
if (stream)
{
fseek(stream, 0, SEEK_SET);
unsigned long lCur;
while (fread(&lCur,4,1,stream) != NULL) //文件一大到这里就只能读一次成功(几M就算大)
{
p[lCur/32] |= 0x1 << (31 - lCur%32);
}
fclose(stream);
}*/
//以下使用内存映射的方法:由于文件有16G那么大,一次全映射也是不行的,要分多次
HFILE hFile;
OFSTRUCT opBuf;
HANDLE hMapfile;
HANDLE hMapview;
BYTE *recv;
hFile=OpenFile("c://SkinPPDemo-VC.msi",&opBuf,OF_READ);
if (hFile==HFILE_ERROR)
{
printf("open file failed!/n");
return ;
}
DWORD dwSizeLow, dwSizeHigh;
dwSizeLow = GetFileSize((HANDLE)hFile, &dwSizeHigh);
hMapfile=CreateFileMapping((HANDLE)hFile,NULL,PAGE_READONLY,0,0,"MapTest");
if(hMapfile==NULL || dwSizeLow == 0)
{
printf("mapping file failed!/n");
return ;
}
CloseHandle((HANDLE)hFile);
hFile=0;
for(unsigned long l=0;l<=dwSizeHigh;l++)
{
const long READ_SIZE_EVERY_TIME = 1024 * 1024;//一次读入1兆
long lReadTimes = l<dwSizeHigh ?
(0xFFFFFFFF/READ_SIZE_EVERY_TIME + 1) : (dwSizeLow/READ_SIZE_EVERY_TIME + 1);
for(unsigned long j=0;j< lReadTimes;j++)
{
if (l<dwSizeHigh || lReadTimes - 1 > j)
hMapview=MapViewOfFile(hMapfile,FILE_MAP_READ,l,
j*READ_SIZE_EVERY_TIME,READ_SIZE_EVERY_TIME);
else //最后一次只映射最后一部分
hMapview=MapViewOfFile(hMapfile,FILE_MAP_READ,l,
j*READ_SIZE_EVERY_TIME,dwSizeLow%READ_SIZE_EVERY_TIME);
if(hMapview==NULL)
{
printf("mapping view failed!/n");
return ;
}
recv=(BYTE *)hMapview;
BYTE *cur=recv;
long lSize = ((lReadTimes - 1 > j) ?
READ_SIZE_EVERY_TIME : (dwSizeLow%READ_SIZE_EVERY_TIME));
while ( (cur - recv) < (lSize - 3))
{
unsigned long lCur = *(unsigned long*)cur;
/*标识哪个数出现过了,bit的索引就代表那个数
* 如果内存小,这句会导致循环很慢,可能因为页面文件和内存交互次数太多
*/
p[lCur/32] |= 0x1 << (31 - lCur%32);
cur += 4;
}
UnmapViewOfFile(hMapview);
}
}
//完成后,扫描一下数组p,找到第一个不存在的数
for(unsigned long i=0;i<(4096/32)*1024*1024;i++)
{
if (p[i] < 0xFFFFFFFF)
{
//找到啦
for(int j=0;j<32;j++)
{
if ((p[i] & (0x1 << (31-j))) == 0)
{
lResult = i * 32 + j;//第一个不存在的数
bFound = true;
break;
}
}
}
if (bFound)
break;
}
delete p;
注:如果直接用new申请动态内存,由于内存高达512兆,会很容易被交换到页面文件上去,效率会急剧下降,所以建议把内存申请在物理内存上,请参考VirtualAlloc和MapUserPhysicalPages(需要SDK)等的使用