现在的位置: 首页 > 综合 > 正文

利用文件映射实现的快速Stream

2013年10月14日 ⁄ 综合 ⁄ 共 14583字 ⁄ 字号 评论关闭

转大富翁笔记  作者: hellbeast

unit BHMapFileStream;

interface
uses Classes,Forms,Windows,SysUtils;

const
  sErrorOpenFileFailue ='打开文件: %s 出错!';
  sErrorMapFileFailue ='创建映象失败!';
  sErrorViewFileFailue ='创建映象视图失败!';

type

  TBHMapFileStream=class(TStream)
  private
    FMapFile:THandle;
    FViewFile:Pointer;
    FSize,FPosition,FCurViewOffset:Longint;
    FInMemory:Boolean;
    FFileName:String;
    procedure CreateInFile(FileName:String;AutoCreate:Boolean=True;FileSize:LongInt=0;ForceCreate:Boolean=False);
    function GetMapSize:LongInt;
    function GetCurViewPos:LongInt;
    procedure Reset(DeleteTempFile:Boolean=True);
  protected
    function ReSetView(ViewOffSet:LongInt):Boolean;
    function PrePareSize(NewSize:LongInt):Boolean;virtual;
    function GetSize: Int64;override;
  public
    constructor Create;overload;
    constructor Create(FileName:String;ForceCreate:Boolean=False);overload;
    destructor Destroy; override;
    procedure Clear;
    function Read(var Buffer; Count: Longint): Longint; override;
    function Seek(Offset: Longint; Origin: Word): Longint; override;
    function CopyFromStream(Stream:TBHMapFileStream):Int64;overload;
    function CopyFromStream(Stream:TStream;Count:Int64):Int64;overload;
    procedure SaveToStream(Stream: TStream);
    procedure SaveToFile(const FileName: string);
    procedure LoadFromStream(Stream: TStream);
    procedure LoadFromFile(const FileName: string);
    procedure SetSize(NewSize: Longint); override;
    procedure SaveData;
    function Write(const Buffer; Count: Longint): Longint; override;
    property Memory: Pointer read FViewFile;
  end;

var
  DefaultAllocBlockSize:LongInt;  //默认缓冲大小,等于系统的内存分配颗粒大小
  DefaultMapFileSize:LongInt;    //默认映象的内存大小,等于$4MB
implementation

function GetSysAllocBlockSize:LongInt;
var
  SystemInfoGet:TSystemInfo;
begin
  GetSystemInfo(SystemInfoGet);
  Result:=SystemInfoGet.dwAllocationGranularity;
end;

function GetPhysicalMemory:LongInt;
var
  MemoryStatusGet:TMemoryStatus;
begin
  GlobalMemoryStatus( MemoryStatusGet);
  Result:=MemoryStatusGet.dwTotalPhys;
end;

procedure BatchMove(Source, Destination: Pointer;
  Count: Integer);
var
  BufSize, N: Integer;
  OffSet:Integer;
  Buffer: PChar;
begin
  if Count > DefaultAllocBlockSize then BufSize := DefaultAllocBlockSize else BufSize := Count;
  GetMem(Buffer, BufSize);
  try
    offset:=0;
    while Count <> 0 do
    begin
      if Count > BufSize then N := BufSize else N := Count;
      Move(Pointer(LongInt(Source)+Offset)^,Buffer^,N);
      Move(Buffer^,Pointer(LongInt(Destination)+Offset)^,N);
      Dec(Count, N);
      Inc(OffSet,N);
      Application.ProcessMessages;
    end;
  finally
    FreeMem(Buffer, BufSize);
  end;
end;

function GetTempFile:string;
var
  pPath,pFile:PAnsiChar;
begin
  GetMem(pPath,255);
  GetTempPath(255,pPath);
  GetMem(pFile,255);
  GetTempFileName(pPath,'mfs',0,pFile);
  Result:=pFile;
  FreeMem(pPath,255);
  FreeMem(pFile,255);
end;

{ TBHMapFileStream }
procedure TBHMapFileStream.Clear;
begin
  SetSize(0);
end;

constructor TBHMapFileStream.Create;
begin
  inherited Create;
  FFileName:=GetTempFile;
  FInMemory:=True;
  CreateInFile(FFileName);
end;

function TBHMapFileStream.CopyFromStream(Stream: TBHMapFileStream):Int64;
var
  iMapCount,iRest,iCurOffset:Integer;
  cntI:Integer;
begin
  Stream.Position := 0;
  Result:=Stream.Size;
//  SetSize(Size+Result);
  iMapCount:=Result div DefaultMapFileSize;
  iRest:=Result mod DefaultMapFileSize;
  for cntI:=0 to iMapCount-1 do
  begin
    iCurOffset:=cntI*DefaultMapFileSize;
    Stream.ReSetView(iCurOffset);
    WriteBuffer(Stream.FViewFile^,DefaultMapFileSize);
  end;
  if iRest>0 then begin
    if iMapCount<>0 then
      Stream.ReSetView(FCurViewOffset+DefaultMapFileSize);
    WriteBuffer(Stream.FViewFile^,iRest);
  end;
end;

constructor TBHMapFileStream.Create(FileName: String;ForceCreate:Boolean);
begin
  inherited Create;
  FFileName:=FileName;
  FInMemory:=False;
  CreateInFile(FileName,True,0,ForceCreate);
end;

procedure TBHMapFileStream.CreateInFile(FileName: String;AutoCreate:Boolean;FileSize:LongInt;ForceCreate:Boolean);
var
  iSize:Longint;
  FOpenFile:THandle;
begin
  if ForceCreate then
    FOpenFile:=CreateFile(PAnsiChar(FileName),GENERIC_WRITE or GENERIC_READ,FILE_SHARE_READ,nil,CREATE_ALWAYS,FILE_FLAG_SEQUENTIAL_SCAN,0)
  else if FileExists(FileName) then
    FOpenFile:=CreateFile(PAnsiChar(FileName),GENERIC_WRITE or GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0)
  else if AutoCreate then
    FOpenFile:=CreateFile(PAnsiChar(FileName),GENERIC_WRITE or GENERIC_READ,FILE_SHARE_READ,nil,CREATE_ALWAYS,FILE_FLAG_SEQUENTIAL_SCAN,0)
  else raise Exception.CreateFmt(sErrorOpenFileFailue,[FileName]);
  if FileSize=0 then
    FSize:=GetFileSize(FOpenFile,nil)
  else
    FSize:=FileSize;
  iSize:=GetMapSize;
  FMapFile:=CreateFileMapping(FOpenFile,nil,PAGE_READWRITE,0,iSize,nil);
  if FMapFile=0 then begin
    CloseHandle(FOpenFile);
    raise Exception.Create(sErrorMapFileFailue );
  end;
  CloseHandle(FOpenFile);  
  FViewFile:=MapViewOfFile(FMapFile,FILE_MAP_ALL_ACCESS,0,0,DefaultMapFileSize);
  if FViewFile=nil then begin
    CloseHandle(FMapFile);
    raise Exception.Create(sErrorViewFileFailue);
  end;
  FCurViewOffset:=0;
  FPosition:=0;
end;

destructor TBHMapFileStream.Destroy;
begin
  Reset(True);
  inherited;
end;

function TBHMapFileStream.GetCurViewPos: LongInt;
begin
  Result:=FPosition-FCurViewOffset;
end;

function TBHMapFileStream.GetMapSize: LongInt;
begin
  if ((FSize mod DefaultMapFileSize)=0) and ((FSize div DefaultMapFileSize )>0) then
    Result:=(FSize div DefaultMapFileSize) *DefaultMapFileSize
  else
    Result:=((FSize div DefaultMapFileSize)+1) *DefaultMapFileSize
end;

function TBHMapFileStream.GetSize: Int64;
begin
  Result:=FSize;
end;

procedure TBHMapFileStream.LoadFromFile(const FileName: string);
var
  hFile,hMapFile:THandle;
  pViewFile:Pointer;
  iSize:LongInt;
  iMapCount,iRest:Integer;
  cntI:Integer;
begin
  hFile:=CreateFile(PAnsiChar(FileName),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0);
  if hFile=INVALID_HANDLE_VALUE then raise Exception.CreateFmt(sErrorOpenFileFailue,[FileName]);
  hMapFile:=CreateFileMapping(hFile,nil,PAGE_READONLY,0,0,nil);
  if hMapFile=0 then begin
    CloseHandle(hFile);
    raise Exception.Create(sErrorMapFileFailue );
  end;
  iSize:=GetFileSize(hFile,nil);
  Clear;
  SetSize(iSize);
  iMapCount:=iSize div DefaultMapFileSize;
  iRest:=iSize mod DefaultMapFileSize;
  pViewFile:=nil;
  Position:=0;
  for cntI:=0 to iMapCount-1 do
  begin
    FCurViewOffset:=cntI*DefaultMapFileSize;
    ReSetView(FCurViewOffset);
    if Assigned(pViewFile) then UnmapViewOfFile(pViewFile);
    pViewFile:=MapViewOfFile(hMapFile,FILE_MAP_READ,0,FCurViewOffset,DefaultMapFileSize);
    if pViewFile=nil then begin
      CloseHandle(hMapFile);
      CloseHandle(hFile);
      raise Exception.Create(sErrorViewFileFailue);
    end;
    BatchMove(pViewFile,FViewFile,DefaultMapFileSize);
  end;
  if iRest>0 then begin
    if iMapCount<>0 then
      ReSetView(FCurViewOffset+DefaultMapFileSize);
    if Assigned(pViewFile) then UnmapViewOfFile(pViewFile);
    pViewFile:=MapViewOfFile(hMapFile,FILE_MAP_READ,0,FCurViewOffset,iRest);
    if pViewFile=nil then begin
      CloseHandle(hMapFile);
      CloseHandle(hFile);
      raise Exception.Create(sErrorViewFileFailue);
    end;
    BatchMove(pViewFile,FViewFile,iRest);
  end;
  if Assigned(pViewFile) then UnmapViewOfFile(pViewFile);
  CloseHandle(hMapFile);
  CloseHandle(hFile);
  Position:=Size;
end;

procedure TBHMapFileStream.LoadFromStream(Stream: TStream);
var
  Count: Longint;
  iMapCount,iRest:Integer;
  cntI:Integer;
  Buffer:PChar;
begin
  Stream.Position := 0;
  Count := Stream.Size;
  SetSize(Count);
  iMapCount:=Count div DefaultMapFileSize;
  iRest:=Count mod DefaultMapFileSize;
  GetMem(Buffer,DefaultMapFileSize);
  try
    for cntI:=0 to iMapCount-1 do
    begin
      FCurViewOffset:=cntI*DefaultMapFileSize;
      ReSetView(FCurViewOffset);
      Stream.ReadBuffer(Buffer^,DefaultMapFileSize);
      BatchMove(Buffer,FViewFile,DefaultMapFileSize);
    end;
    if iRest>0 then begin
      if iMapCount<>0 then
        ReSetView(FCurViewOffset+DefaultMapFileSize);
      Stream.ReadBuffer(Buffer^,iRest);
      BatchMove(Buffer,FViewFile,iRest);
    end;
  finally
    FreeMem(Buffer,DefaultMapFileSize);
  end;
end;

function TBHMapFileStream.PrePareSize(NewSize: Integer): Boolean;
begin
  Result:=True;
  if (NewSize div DefaultAllocBlockSize+1)<=(FSize div DefaultMapFileSize +1) then begin
    FSize:=NewSize;
  end;
  try
    Reset(False);
    CreateInFile(FFileName,False,NewSize,False);
  except
    Result:=False;
  end;
end;

function TBHMapFileStream.Read(var Buffer; Count: Integer): Longint;
var
  iMapCount,iRest,iLast,iCurOffset:Integer;
  cntI:Integer;
begin
  if (FPosition >= 0) and (Count >= 0) then
  begin
    Result := FSize - FPosition;
    if Result > 0 then
    begin
      if Result > Count then Result := Count;
      iRest:=FCurViewOffset+DefaultMapFileSize-FPosition;
      iCurOffset:=0;
      if iRest>=Result then begin
        BatchMove(Pointer(Longint(FViewFile) + GetCurViewPos), @Buffer, Result);
      end
      else begin
        BatchMove(Pointer(Longint(FViewFile) + GetCurViewPos), @Buffer, iRest);
        Inc(iCurOffset,iRest);
        iMapCount:=(Result-iRest) div DefaultMapFileSize;
        iLast:=Result-(iMapCount*DefaultMapFileSize+iRest);
        for cntI:=0 to iMapCount-1 do
        begin
          ReSetView(FCurViewOffset+DefaultMapFileSize);
          BatchMove(FViewFile, Pointer(Integer(@Buffer)+iCurOffset), DefaultMapFileSize);
          Inc(iCurOffset,DefaultMapFileSize);
        end;
        if iLast>0 then begin
          ReSetView(FCurViewOffset+DefaultMapFileSize);
          BatchMove(FViewFile, Pointer(Integer(@Buffer)+iCurOffset), iLast);
        end;
      end;
      Inc(FPosition, Result);
      Exit;
    end;
  end;
  Result := 0;
end;

procedure TBHMapFileStream.Reset(DeleteTempFile:Boolean);
var
  FOpenFile:THandle;
begin
  if Assigned(FViewFile) then begin
    UnmapViewOfFile(FViewFile);
    CloseHandle(FMapFile);
    FOpenFile:=CreateFile(PAnsiChar(FFileName),GENERIC_WRITE or GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0);
    if FInMemory  and DeleteTempFile then
      SetFilePointer(FOpenFile,1,nil,FILE_BEGIN)
    else
      SetFilePointer(FOpenFile,FSize,nil,FILE_BEGIN);
    SetEndOfFile(FOpenFile);
    CloseHandle(FOpenFile);
    if FInMemory and DeleteTempFile then DeleteFile(FFileName);
  end;
end;

function TBHMapFileStream.ReSetView(ViewOffSet: Integer):Boolean;
begin
  Result:=False;
  if ViewOffSet>GetMapSize then Exit;
  if Assigned(FViewFile) then begin
    try
      if ViewOffSet<>FCurViewOffset then begin
        UnmapViewOfFile(FViewFile);
        FViewFile:=MapViewOfFile(FMapFile,FILE_MAP_ALL_ACCESS,0,ViewOffSet,DefaultMapFileSize);
        FCurViewOffset:=ViewOffSet;
      end;
      Result:=True;
    except
      Result:=False;
    end;
  end
  else
    Result:=False;
end;

procedure TBHMapFileStream.SaveData;
begin
  if FViewFile<>nil then FlushViewOfFile(FViewFile,0);
end;

procedure TBHMapFileStream.SaveToFile(const FileName: string);
var
  hFile,hMapFile:THandle;
  pViewFile:Pointer;
  oldPos,iMapCount,iRest:Integer;
  cntI:Integer;
begin
  hFile:=CreateFile(PAnsiChar(FileName),GENERIC_WRITE or GENERIC_READ,FILE_SHARE_READ,nil,CREATE_ALWAYS,FILE_FLAG_SEQUENTIAL_SCAN,0);
  if hFile=INVALID_HANDLE_VALUE then raise Exception.CreateFmt(sErrorOpenFileFailue,[FileName]);
  hMapFile:=CreateFileMapping(hFile,nil,PAGE_READWRITE,0,Size,nil);
  if hMapFile=0 then begin
    CloseHandle(hFile);
    raise Exception.Create(sErrorMapFileFailue );
  end;
  CloseHandle(hFile);
  iMapCount:=Size div DefaultMapFileSize;
  iRest:=Size mod DefaultMapFileSize;
  pViewFile:=nil;
  oldPos:=FPosition;
  Position:=0;
  for cntI:=0 to iMapCount-1 do
  begin
    FCurViewOffset:=cntI*DefaultMapFileSize;
    ReSetView(FCurViewOffset);
    if Assigned(pViewFile) then UnmapViewOfFile(pViewFile);
    pViewFile:=MapViewOfFile(hMapFile,FILE_MAP_ALL_ACCESS,0,FCurViewOffset,DefaultMapFileSize);
    if pViewFile=nil then begin
      CloseHandle(hMapFile);
      Position:=oldPos;
      raise Exception.Create(sErrorViewFileFailue);
    end;
    BatchMove(FViewFile,pViewFile,DefaultMapFileSize);
  end;
  if iRest>0 then begin
    if iMapCount<>0 then
      ReSetView(FCurViewOffset+DefaultMapFileSize);
    if Assigned(pViewFile) then UnmapViewOfFile(pViewFile);
    pViewFile:=MapViewOfFile(hMapFile,FILE_MAP_ALL_ACCESS,0,FCurViewOffset,iRest);
    if pViewFile=nil then begin
      CloseHandle(hMapFile);
      Position:=oldPos;
      raise Exception.Create(sErrorViewFileFailue);
    end;
    BatchMove(FViewFile,pViewFile,iRest);
  end;
  if Assigned(pViewFile) then UnmapViewOfFile(pViewFile);
  CloseHandle(hMapFile);
  Position:=oldPos;
end;

procedure TBHMapFileStream.SaveToStream(Stream: TStream);
var
  iMapCount,iRest:Integer;
  cntI:Integer;
  Buffer:PChar;
begin
  if FSize=0 then Exit;
  iMapCount:=FSize div DefaultMapFileSize;
  iRest:=FSize mod DefaultMapFileSize;
  GetMem(Buffer,DefaultMapFileSize);
  try
    Stream.Size:=FSize;
    Stream.Position:=0;
    for cntI:=0 to iMapCount-1 do
    begin
      FCurViewOffset:=cntI*DefaultMapFileSize;
      ReSetView(FCurViewOffset);
      BatchMove(FViewFile,Buffer,DefaultMapFileSize);
      Stream.WriteBuffer(Buffer^,DefaultMapFileSize);
    end;
    if iRest>0 then begin
      if iMapCount<>0 then
        ReSetView(FCurViewOffset+DefaultMapFileSize);
      BatchMove(FViewFile,Buffer,iRest);
      Stream.WriteBuffer(Buffer^,iRest);
    end;
  finally
    FreeMem(Buffer,DefaultMapFileSize);
  end;
end;

function TBHMapFileStream.Seek(Offset: Integer; Origin: Word): Longint;
begin
  case Origin of
    soFromBeginning: FPosition := Offset;
    soFromCurrent: Inc(FPosition, Offset);
    soFromEnd: FPosition := FSize + Offset;
  end;
  ReSetView((FPosition div DefaultMapFileSize)*DefaultMapFileSize);
  Result := FPosition;
end;

procedure TBHMapFileStream.SetSize(NewSize: Integer);
var
  oldPos:LongInt;
begin
  oldPos:=FPosition;
  if PrePareSize(NewSize) then
    if oldPos>NewSize then
      Seek(0,soFromEnd)
    else
      Seek(oldPos,soFromBeginning);
end;

function TBHMapFileStream.Write(const Buffer; Count: Integer): Longint;
var
  iMapCount,iRest,iLast,iCurOffset:Integer;
  cntI:Integer;
  Pos: Longint;
begin
  if (FPosition >= 0) and (Count >= 0) then
  begin
    Pos := FPosition + Count;
    if Pos > 0 then
    begin
      if Pos > FSize then
        SetSize(Pos);
      iRest:=FCurViewOffset+DefaultMapFileSize-FPosition;
      iCurOffset:=0;
      if iRest>=Count then begin
        BatchMove(@Buffer,Pointer(Longint(FViewFile) + GetCurViewPos),  Count);
      end
      else begin
        BatchMove(@Buffer,Pointer(Longint(FViewFile) + GetCurViewPos),  iRest);
        Inc(iCurOffset,iRest);
        iMapCount:=(Count-iRest) div DefaultMapFileSize;
        iLast:=Count-(iMapCount*DefaultMapFileSize+iRest);
        for cntI:=0 to iMapCount-1 do
        begin
          ReSetView(FCurViewOffset+DefaultMapFileSize);
          BatchMove(Pointer(Integer(@Buffer)+iCurOffset),FViewFile, DefaultMapFileSize);
          Inc(iCurOffset,DefaultMapFileSize);
        end;
        if iLast>0 then begin
          ReSetView(FCurViewOffset+DefaultMapFileSize);
          BatchMove(Pointer(Integer(@Buffer)+iCurOffset),FViewFile,  iLast);
        end;
      end;
      FPosition := Pos;
      Result := Count;
      Exit;
    end;
  end;
  Result := 0;
end;

function TBHMapFileStream.CopyFromStream(Stream: TStream;Count:Int64): Int64;
var
  BufSize, N: Integer;
  Buffer: PChar;
begin
  if Count = 0 then
  begin
    Stream.Position := 0;
    Count := Stream.Size;
  end;
  Result := Count;
//  SetSize(Size+Result);
  if Count > DefaultAllocBlockSize then BufSize := DefaultAllocBlockSize else BufSize := Count;
  GetMem(Buffer, DefaultAllocBlockSize);
  try
    while Count <> 0 do
    begin
      if Count > BufSize then N := BufSize else N := Count;
      Stream.ReadBuffer(Buffer^, N);
      WriteBuffer(Buffer^, N);
      Dec(Count, N);
    end;
  finally
    FreeMem(Buffer, DefaultAllocBlockSize);
  end;
end;

initialization
  DefaultAllocBlockSize:=GetSysAllocBlockSize;
  DefaultMapFileSize:=$00100000;
finalization

end.

抱歉!评论已关闭.