在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
delphi windows内存映射 使用内存映射文件读写大文件 几十GB、几百GB、乃至几TB的海量存储,再以通常的文件处理方法进行处理显然是行不通的。使用字符串变量的方法不仅会加重内存的负担,而且会Unicode和ASCII码的转换会把你弄得焦头烂额。目前,对于上述这种大文件的操作一般是以内存映射文件的方式来加以处理的,比I/O读写要快20倍,所谓I/O操作不是对外围设备直接进行操作,而是对设备与cpu连接的接口电路的操作。而映射文件的方法就是对磁盘直接进行操作。 内存映射为什么速度快? 使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,这意味着在对文件进行处理时将不必再为文件申请并分配缓存,所有的文件缓存操作均由系统直接管理,由于取消了将文件数据加载到内存、数据从内存到文件的回写以及释放内存块等步骤,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。 在多个进程之间共享数据 如果数据量小,处理方法是灵活多变的,如果共享数据容量巨大,那么就需要借助于内存映射文件来进行。实际上,内存映射文件正是解决本地多个进程间数据共享的最有效方法。 内存映射流程 首先要通过CreateFile()来创建或打开一个文件内核对象,这个对象标识了磁盘上将要用作内存映射文件的文件。在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后,只指定了映像文件的路径,映像的长度还没有指定。为了指定文件映射对象需要多大的物理存储空间还需要通过CreateFileMapping()来创建一个文件映射内核对象以告诉系统文件的尺寸以及访问文件的方式。 内存映射处理巨型文件 十几GB乃至几十GB容量的巨型文件,而一个32位进程所拥有的虚拟地址空间只有2^32 = 4GB,显然不能一次将文件映像全部映射进来。对于这种情况只能依次将大文件的各个部分映射到进程中的一个较小的地址空间。这需要对上面的一般流程进行适当的更改: 处理大文件例子 平时很少使用大文件的内存映射,碰巧遇到了这样的要求,所以把过程记录下来,当给各位一个引子吧,因为应用不算复杂,可能有考虑不到的地方,欢迎交流。 procedure TGetDataThread.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); if FFile_Handle = INVALID_HANDLE_VALUE then Exit; //获取文件尺寸 filesize := GetFileSize(FFile_Handle,nil); //创建映射 FFile_Map := CreateFileMapping(FFile_Handle,nil,PAGE_READONLY,0,0,nil); if FFile_Map = 0 then Exit; //此处我们已10倍blocksize为一个数据块来映射,如果文件尺寸小于10倍blocksize,则直接映射整个文件长度 if filesize div blocksize > 10 then readlen := 10*blocksize else readlen := filesize; for i := 0 to FInfoList.Count - 1 do 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,那么我们要保留之前映射末尾的内容,以便和新映射的内容合并 if i > 0 then begin offset := offset + readlen; //写入临时流 tstream.Write(p^,readlen-totallen); tstream.Position := 0; end; //如果未读取的数据长度已经不够一个分配粒度,那么就直接映射剩下的长度 if filesize-offset < blocksize then readlen := filesize-offset; //映射,p是指向映射区域的指针 //注意这里第三个参数,一直设为0,这个值要根据实际情况设置 p := PChar(MapViewOfFile(FFile_Map,FILE_MAP_READ,0,offset,readlen)); end; //如果临时流中有数据,需要合并 if tstream.Size > 0 then 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; 如何将一整个文件读入内存,文件大小有64M function FastReadFile(FileName: string): Integer; const PAGE_SIZE = 4 * 1024; //映射块大小不易过大,尽量以4k对齐 var hFile: THandle; szHigh,szLow: DWORD; szFile,ps: Int64; hMap: THandle; hData: Pointer; dwSize: Cardinal; begin Result := -1; hFile := 0; hMap := 0; hData := nil; szHigh := 0; try //打开已存在的文件,获得文件句柄 hFile := CreateFile(PChar(FileName),GENERIC_READ or GENERIC_WRITE,FILE_SHARE_READ, nil,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0); if hFile = 0 then begin Result := GetLastError; Exit; end; //获取文件大小 hMap := 0; hData := nil; szHigh := 0; szLow := GetFileSize(hFile,@szHigh); szFile := szLow or (szHigh shl 32); //创建映射句柄 hMap := CreateFileMapping(hFile, nil, PAGE_READWRITE, szHigh, szLow, nil); if hMap = 0 then begin Result := GetLastError; Exit; end; ps := 0; //文件可能比较大,分块进行映射 while ps < szFile do begin //计算映射大小及位置 if szFile - ps > PAGE_SIZE then dwSize := PAGE_SIZE else dwSize := szFile - ps; szLow := ps and $FFFFFFFF; szHigh := ps shr 32; //进行映射 hData := MapViewOfFile(hMap,FILE_MAP_ALL_ACCESS,szHigh,szLow,dwSize); if hData = nil then Break; try //此时文件偏移ps处的数据通过hData即可读取到,块大小为dwSize //以下加上你读取的代码,可以做一个回调函数 //比如你要当前位置的数据(取文件数)拷到指定内存处 CopyMemory(目标地址指针,hData,dwSize); // finally //移动文件偏移位置 ps := ps + dwSize; //释放映射块 UnmapViewOfFile(hData); hData := nil; end; end; finally //释放必要资源 if hData <> nil then UnmapViewOfFile(hData); if hMap <> 0 then CloseHandle(hMap); if hFile <> 0 then CloseHandle(hFile); end; end; Delphi内存映射文件例子 unit FileMap; interface uses Windows,Messages,SysUtils,Classes,Graphics,Controls,Forms,StdCtrls,Dialogs; type TFileMap=class(TComponent) private FMapHandle:THandle; //内存映射文件句柄 FMutexHandle:THandle; //互斥句柄 FMapName:string; //内存映射对象 FSynchMessage:string; //同步消息 FMapStrings:TStringList; //存储映射文件信息 FSize:DWord; //映射文件大小 FMessageID:DWord; //注册的消息号 FMapPointer:PChar; //映射文件的数据区指针 FLocked:Boolean; //锁定 FIsMapOpen:Boolean; //文件是否打开 FExistsAlready:Boolean; //是否已经建立过映射文件 FReading:Boolean; //是否正在读取内存文件数据 FAutoSynch:Boolean; //是否同步 FOnChange:TNotifyEvent; //当内存数据区内容改变时 FFormHandle:Hwnd; //存储本窗口的窗口句柄 FPNewWndHandler:Pointer; FPOldWndHandler:Pointer; procedure SetMapName(Value:string); procedure SetMapStrings(Value:TStringList); procedure SetSize(Value:DWord); procedure SetAutoSynch(Value:Boolean); procedure EnterCriticalSection; procedure LeaveCriticalSection; procedure MapStringsChange(Sender:TObject); procedure NewWndProc(var FMessage:TMessage); public constructor Create(AOwner:TComponent);override; destructor Destroy;override; procedure OpenMap; procedure CloseMap; procedure ReadMap; procedure WriteMap; property ExistsAlready:Boolean read FExistsAlready; property IsMapOpen:Boolean read FIsMapOpen; published property MaxSize:DWord read FSize write SetSize; property AutoSynchronize:Boolean read FAutoSynch write SetAutoSynch; property MapName:string read FMapName write SetMapName; property MapStrings:TStringList read FMapStrings write SetMapStrings; property OnChange:TNotifyEvent read FOnChange write FOnChange; end; implementation constructor TFileMap.Create(AOwner:TComponent); begin inherited Create(AOwner); FAutoSynch:=True; FSize:=4096; FReading:=False; FMapStrings:=TStringList.Create; FMapStrings.OnChange:=MapStringsChange; FMapName:='Unique & Common name'; FSynchMessage:=FMapName+'Synch-Now'; if AOwner is TForm then begin FFormHandle:=(AOwner as TForm).Handle; FPOldWndHandler:=Ptr(GetWindowLong(FFormHandle,GWL_wNDPROC)); FPNewWndHandler:=MakeObjectInstance(NewWndProc); if FPNewWndHandler=nil then raise Exception.Create('超出资源'); SetWindowLong(FFormHandle,GWL_WNDPROC,Longint(FPNewWndHandler)); end else raise Exception.Create('组件的所有者应该是TForm'); end; destructor TFileMap.Destroy; begin CloseMap; SetWindowLong(FFormHandle,GWL_WNDPROC,Longint(FPOldWndHandler)); if FPNewWndHandler<>nil then FreeObjectInstance(FPNewWndHandler); FMapStrings.Free; FMapStrings:=nil; inherited destroy; end; procedure TFileMap.OpenMap; var TempMessage:array[0..255] of Char; begin if (FMapHandle=0) and (FMapPointer=nil) then begin FExistsAlready:=False; FMapHandle:=CreateFileMapping($FFFFFFFF,nil,PAGE_READWRITE,0,FSize,PChar(FMapName)); if (FMapHandle=INVALID_HANDLE_VALUE) or (FMapHandle=0) then raise Exception.Create('创建文件映射对象失败!') else begin if (FMapHandle<>0) and (GetLastError=ERROR_ALREADY_EXISTS) then FExistsAlready:=True; //如果已经建立的话,就设它为TRUE; FMapPointer:=MapViewOfFile(FMapHandle,FILE_MAP_ALL_ACCESS,0,0,0); if FMapPointer=nil then raise Exception.Create('映射文件的视图到进程的地址空间失败') else begin StrPCopy(TempMessage,FSynchMessage); FMessageID:=RegisterWindowMessage(TempMessage); if FMessageID=0 then raise Exception.Create('注册消息失败') end end; FMutexHandle:=Windows.CreateMutex(nil,False,PChar(FMapName+'.Mtx')); if FMutexHandle=0 then raise Exception.Create('创建互斥对象失败'); FIsMapOpen:=True; if FExistsAlready then //判断内存文件映射是否已打开 ReadMap else WriteMap; end; end; procedure TFileMap.CloseMap; begin if FIsMapOpen then begin if FMutexHandle<>0 then begin CloseHandle(FMutexHandle); FMutexHandle:=0; end; if FMapPointer<>nil then begin UnMapViewOfFile(FMapPointer); FMapPointer:=nil; end; if FMapHandle<>0 then begin CloseHandle(FMapHandle); FMapHandle:=0; end; FIsMapOpen:=False; end; end; procedure TFileMap.ReadMap; begin FReading:=True; if(FMapPointer<>nil) then FMapStrings.SetText(FMapPointer); end; procedure TFileMap.WriteMap; var StringsPointer:PChar; HandleCounter:integer; SendToHandle:HWnd; begin if FMapPointer<>nil then begin StringsPointer:=FMapStrings.GetText; EnterCriticalSection; if StrLen(StringsPointer)+1<=FSize then System.Move(StringsPointer^,FMapPointer^,StrLen(StringsPointer)+1) else raise Exception.Create('写字符串失败,字符串太大!'); LeaveCriticalSection; SendMessage(HWND_BROADCAST,FMessageID,FFormHandle,0); StrDispose(StringsPointer); end; end; procedure TFileMap.MapStringsChange(Sender:TObject); begin if FReading and Assigned(FOnChange) then FOnChange(Self) else if (not FReading) and FIsMapOpen and FAutoSynch then WriteMap; end; procedure TFileMap.SetMapName(Value:string); begin if (FMapName<>Value) and (FMapHandle=0) and (Length(Value)<246) then begin FMapName:=Value; FSynchMessage:=FMapName+'Synch-Now'; end; end; procedure TFileMap.SetMapStrings(Value:TStringList); begin if Value.Text<>FMapStrings.Text then begin if Length(Value.Text)<=FSize then FMapStrings.Assign(Value) else raise Exception.Create('写入值太大'); end; end; procedure TFileMap.SetSize(Value:DWord); var StringsPointer:PChar; begin if (FSize<>Value) and (FMapHandle=0) then begin StringsPointer:=FMapStrings.GetText; if (Value<StrLen(StringsPointer)+1) then FSize:=StrLen(StringsPointer)+1 else FSize:=Value; if FSize<32 then FSize:=32; StrDispose(StringsPointer); end; end; procedure TFileMap.SetAutoSynch(Value:Boolean); begin if FAutoSynch<>Value then begin FAutoSynch:=Value; if FAutoSynch and FIsMapOpen then WriteMap; end; end; procedure TFileMap.EnterCriticalSection; begin if (FMutexHandle<>0) and not FLocked then begin FLocked:=(WaitForSingleObject(FMutexHandle,INFINITE)=WAIT_OBJECT_0); end; end; procedure TFileMap.LeaveCriticalSection; begin if (FMutexHandle<>0) and FLocked then begin ReleaseMutex(FMutexHandle); FLocked:=False; end; end; //消息捕获过程 procedure TFileMap.NewWndProc(var FMessage:TMessage); begin with FMessage do begin if FIsMapOpen if (Msg=FMessageID) and (WParam<>FFormHandle) then ReadMap; Result:=CallWindowProc(FPOldWndHandler,FFormHandle,Msg,wParam,lParam); end; end;end.
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论