內存映射大文件


對於一些小文件,用普通的文件流就可以很好的解決,可是對於超大文件,比如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;  

http://www.cnblogs.com/hnxxcxg/archive/2012/11/04/2753265.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM