Delphi 中内存映射对于大文件的使用
Delphi中内存映射对于大文件的使用
平时很少使用大文件的内存映射,碰巧遇到了这样的要求,所以把过程记录下来,当给各位一个引子吧,因为应用不算复杂,可能有考虑不到的地方,欢迎交流。
对于一些小文件,用普通的文件流就可以很好的解决,可是对于超大文件,比如2G或者更多,文件流就不行了,所以要使用API的内存映射的相关方法,即使是内存映射,也不能一次映射全部文件的大小,所以必须采取分块映射,每次处理一小部分。
先来看几个函数
CreateFile:打开文件
GetFileSize:获取文件尺寸
CreateFileMapping:创建映射
MapViewOfFile:映射文件
看MapViewOfFile的帮助,他的最后两个参数都需要是页面粒度的整数倍,一般机器的页面粒度为64k(65536字节),而我们实际操作中,一般都不是这样规矩的,任意位置,任意长度都是可能的,所以就要做一些处理。
本例的任务是从一个长度列表中(FInfoList),依次读取长度值,然后到另外一个大文件(FSourceFileName)中去顺序读取指定长度的数据,如果是小文件,这个就好办了,一次读到文件流中,然后依次读取就是了,大数对于大文件,就需要不断改变映射的位置,来取得我们想要的数据。
本例中显示先通过GetSystemInfo来获取页面粒度,然后以10倍的页面粒度为一个映射数据块,在for循环中,会判断已经读取的长度(totallen)加上即将读取的长度,是否在本次映射范围之内(10倍的页面粒度),如果在就继续读取,如果超出了,就要记下剩下的数据,然后重新映射下一块内存,并将记录下的剩余数据合并到新读取的数据中,有点绕啊(可能是我的想法太绕了),
下面列出代码。
procedureTGetDataThread.DoGetData; var FFile_Handle:THandle; FFile_Map:THandle; list:TStringList; p:PChar; i,interval:Integer; begin try totallen:=0; offset:=0; tstream:=TMemoryStream.Create; stream:=TMemoryStream.Create; list:=TStringList.Create; //获取系统信息 GetSystemInfo(sysinfo); //页面分配粒度大小 blocksize:=sysinfo.dwAllocationGranularity; //打开文件 FFile_Handle:=CreateFile(PChar(FSourceFileName),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); ifFFile_Handle=INVALID_HANDLE_VALUEthenExit; //获取文件尺寸 filesize:=GetFileSize(FFile_Handle,nil); //创建映射 FFile_Map:=CreateFileMapping(FFile_Handle,nil,PAGE_READONLY,0,0,nil); ifFFile_Map=0thenExit; //此处我们已10倍blocksize为一个数据块来映射,如果文件尺寸小于10倍blocksize,则直接映射整个文件长度 iffilesizedivblocksize>10then readlen:=10*blocksize else readlen:=filesize; fori:=0toFInfoList.Count-1do begin list.Delimiter:=':'; list.DelimitedText:=FInfoList.Strings[i]; //取得长度,我这里做了解析,因为我存储的信息为a:b:c这种类型,所以以:号分隔 len:=StrToInt(list.Strings[1]); interval:=StrToInt(list.Strings[2]); if(i=0)or(totallen+len>=readlen)then begin //如果已读取的长度加上即将要读取的长度大于10倍blocksize,那么我们要保留之前映射末尾的内容,以便和新映射的内容合并 ifi>0then begin offset:=offset+readlen; //写入临时流 tstream.Write(p^,readlen-totallen); tstream.Position:=0; end; //如果未读取的数据长度已经不够一个分配粒度,那么就直接映射剩下的长度 iffilesize-offset0then begin //把临时流数据copy过来 stream.CopyFrom(tstream,tstream.Size); //然后在末尾写入新数据,合并完成 stream.Write(p^,len-tstream.Size); totallen:=len-tstream.Size; //移动指针的位置,指向下一个数据的开始 Inc(p,len-tstream.Size); tstream.Clear; end else begin stream.Write(p^,len); totallen:=totallen+len; Inc(p,len); end; stream.Position:=0; //将流保存成文件 stream.SaveToFile(IntToStr(i)+'.txt'); stream.Clear; end; finally stream.Free; tstream.Free; CloseHandle(FFile_Handle); CloseHandle(FFile_Map); end; end;
如有疑问请留言或者到本站社区交流讨论,感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!