Delphi 中記憶體映射對於大檔案的使用
平常很少使用大檔案的記憶體映射,碰巧遇到了這樣的要求,所以把過程記錄下來,當給各位一個引子吧,因為應用不算複雜,可能有考慮不到的地方,歡迎交流。
對於一些小文件,用普通的文件流就可以很好的解決,可是對於超大文件,比如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 Stream= Streamemory Stream. TMemoryStream.Create; list := TStringList.Create; //取得系統資訊GetSystemInfo(sysinfo); //頁面分配粒度大小blocksize := sysinfo.dwAllocationGranularity; //開啟檔案FFile_Handle := CreateFile(PChar(FSourceFileName),GENERIC_READ,FILE_SHARE_READ, N,FI,FIEN,FIEN,FIEN_WE_SHA. ,0); if FFile_Handle = INVALID_HANDLE_VALUE then Exit; //取得檔案尺寸filesize := GetFileSize(FFile_Handle,nil); //建立對應FFile_Map := CreateFileMap(FFile_Handle,nil,PAGE_READONLY,0,0,nil); //這裡我們已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;
如有疑問請留言或到本站社區交流討論,感謝閱讀,希望能幫助大家,謝謝大家對本站的支持!