delphi windows内存映射

delphi windows内存映射

使用内存映射文件读写大文件

几十GB、几百GB、乃至几TB的海量存储,再以通常的文件处理方法进行处理显然是行不通的。使用字符串变量的方法不仅会加重内存的负担,而且会Unicode和ASCII码的转换会把你弄得焦头烂额。目前,对于上述这种大文件的操作一般是以内存映射文件的方式来加以处理的,比I/O读写要快20倍,所谓I/O操作不是对外围设备直接进行操作,而是对设备与cpu连接的接口电路的操作。而映射文件的方法就是对磁盘直接进行操作。

内存映射为什么速度快?

使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,这意味着在对文件进行处理时将不必再为文件申请并分配缓存,所有的文件缓存操作均由系统直接管理,由于取消了将文件数据加载到内存、数据从内存到文件的回写以及释放内存块等步骤,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。

在多个进程之间共享数据

如果数据量小,处理方法是灵活多变的,如果共享数据容量巨大,那么就需要借助于内存映射文件来进行。实际上,内存映射文件正是解决本地多个进程间数据共享的最有效方法。

内存映射流程

首先要通过CreateFile()来创建或打开一个文件内核对象,这个对象标识了磁盘上将要用作内存映射文件的文件。在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后,只指定了映像文件的路径,映像的长度还没有指定。为了指定文件映射对象需要多大的物理存储空间还需要通过CreateFileMapping()创建一个文件映射内核对象以告诉系统文件的尺寸以及访问文件的方式。
  CreateFileMapping()在创建了文件映射对象后,还必须为文件数据保留一个地址空间区域,并把文件数据作为映射到该区域的物理存储器进行提交。由MapViewOfFile()负责通过系统的管理而将文件映射对象的全部或部分映射到进程地址空间,实际上相当于加载文件中指定的数据到内存中。此时,对内存映射文件的使用和处理同通常加载到内存中的文件数据的处理方式基本一样,在完成了对内存映射文件的使用时,还要通过一系列的操作完成对其的清除和使用过资源的释放。这部分相对比较简单,可以通过UnmapViewOfFile()完成从进程的地址空间撤消文件数据的映像、通过CloseHandle()关闭前面创建的文件映射对象和文件对象

内存映射处理巨型文件

十几GB乃至几十GB容量的巨型文件,而一个32位进程所拥有的虚拟地址空间只有2^32 = 4GB,显然不能一次将文件映像全部映射进来。对于这种情况只能依次将大文件的各个部分映射到进程中的一个较小的地址空间。这需要对上面的一般流程进行适当的更改:
  1)映射从文件开头的映像;
  2)对该映像进行访问;
  3)取消此映像;
  4)映射一个从文件中的一个更深的位移开始的新映像;
  5)重复步骤2,直到访问完全部的文件数据。

处理大文件例子

平时很少使用大文件的内存映射,碰巧遇到了这样的要求,所以把过程记录下来,当给各位一个引子吧,因为应用不算复杂,可能有考虑不到的地方,欢迎交流。
对于一些小文件,用普通的文件流就可以很好的解决,可是对于超大文件,比如2G或者更多,文件流就不行了,所以要使用API的内存映射的相关方法,即使是内存映射,也不能一次映射全部文件的大小,所以必须采取分块映射,每次处理一小部分。
先来看几个函数
CreateFile :打开文件
GetFileSize : 获取文件尺寸
CreateFileMapping :创建映射
MapViewOfFile :映射文件
看MapViewOfFile的帮助,他的最后两个参数都需要是页面粒度的整数倍,一般机器的页面粒度为64k(65536字节),而我们实际操作中,一般都不是这样规矩的,任意位置,任意长度都是可能的,所以就要做一些处理。
本例的任务是从一个长度列表中(FInfoList),依次读取长度值,然后到另外一个大文件(FSourceFileName)中去顺序读取指定长度的数据,如果是小文件,这个就好办了,一次读到文件流中,然后依次读取就是了,大数对于大文件,就需要不断改变映射的位置,来取得我们想要的数据。
本例中显示先通过GetSystemInfo来获取页面粒度,然后以10倍的页面粒度为一个映射数据块,在for循环中,会判断已经读取的长度(totallen)加上即将读取的长度,是否在本次映射范围之内(10倍的页面粒度),如果在就继续读取,如果超出了,就要记下剩下的数据,然后重新映射下一块内存,并将记录下的剩余数据合并到新读取的数据中,有点绕啊(可能是我的想法太绕了),下面列出代码。

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. 

  

 

posted @ 2020-12-08 16:37  delphi中间件  阅读(542)  评论(0编辑  收藏  举报