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倍的页面粒度),如果在就继续读取,如果超出了,就要记下剩下的数据,然后重新映射下一块内存,并将记录下的剩余数据合并到新读取的数据中,有点绕啊(可能是我的想法太绕了),下面列出代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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内存映射文件例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
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 @   delphi中间件  阅读(556)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2019-12-08 DELPHI开始支持LINUX DOCKER
2018-12-08 mariadb设置初始密码
2015-12-08 IDFTP连不上FTP服务器的解决方法
2015-12-08 SQLServer到底支持多少连接数的并发?
2013-12-08 应用服务器负载平衡集群
点击右上角即可分享
微信分享提示