程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> 一個讀取速度超快的FileStream

一個讀取速度超快的FileStream

編輯:Delphi

  最近一直為自己制作的相冊軟件(http://www.tonixsoft.com/ultraalbum/index.PHP?lang=chs)打開大文件時速度慢而郁悶,我以前的做法是先用TFileStream打開一個文件,然後在其中找到其中的數據段,把其中內容復制給一個TMemoryStream,之所以要再將它復制給一個獨立的TMemoryStream是因為,後續處理的一個文件型數據庫組件必須接受一整個TStream,作為其存儲媒介,整個過程簡直慢得無法忍受。
  
  之所以速度慢,是有兩方面的原因:
  1。用TFileStream打開文件,操作系統在打開文件後會為文件生成內存鏡像,文件一大,那麼開辟空間以及內存拷貝的工作就會變得極為緩慢。
  2。將TFileStream中的一部分再復制給TMemoryStream,這個復制過程會開辟新的內存再進行復制,理所當然內存大了,復制時間也會變長。
  
  我決心針對目前我所遇到的問題,再寫一個文件讀取類,目前就叫TFastFileStream吧,它必須從TStream繼承而來,這樣才能和其它組件方便地結合起來。
  
  首先,要解決的是打開大文件慢的問題,對於這個,使用MapViewOfFile(),將文件直接當作內存鏡像來訪問就可以了,關於MapVIEwOfFile(),以及文件內存鏡像,可以參考這篇文章:http://www.vccode.com/file_show.PHP?id=2409
  
  Delphi下建立文件鏡像的方法為:
  constructor TFastFileStream.Create(const AFileName:String);
  var
    FileSizeHigh:LongWord;
  begin
    FFileHandle:=CreateFile(PChar(AFileName),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
    if FFileHandle=INVALID_HANDLE_VALUE then begin
      raise FastFileStreamException.Create('Error when open file');
    end;
    FSize:=GetFileSize(FFileHandle,@FileSizeHigh);
    if FSize=INVALID_FILE_SIZE then begin
      raise FastFileStreamException.Create('Error when get file size');
    end;
    FMappingHandle:=CreateFileMapping(FFileHandle,nil,PAGE_READONLY,0,0,nil);
    if FMappingHandle=0 then begin
      raise FastFileStreamException.Create('Error when mapping file');
    end;
    FMemory:=MapVIEwOfFile(FMappingHandle,FILE_MAP_READ,0,0,0);
    if FMemory=nil then begin
      raise FastFileStreamException.Create('Error when map vIEw of file');
    end;
  end;
  

  最後,被做成鏡像的數據就存放在FMemory中了,然後,覆蓋TStream的Read()方法,當外部需要取得數據時,讓它到FMemory中去取:
  function TFastFileStream.Read(var Buffer;Count:LongInt):LongInt;
  begin
    if (FPosition >= 0) and (Count >= 0) then
    begin
      Result := FSize - FPosition;
      if Result > 0 then
      begin
        if Result > Count then Result := Count;
        //Move(Pointer(Longint(FMemory) + FPosition)^, Buffer, Result);
        CopyMemory(Pointer(@Buffer),Pointer(LongInt(FMemory)+FPosition),Result);
        Inc(FPosition, Result);
        Exit;
      end;
    end;
    Result := 0;
  end;
  
這段函數主要還是模仿TCustomMemoryStream中的同名方法來寫的,但是有一點比較奇怪,當我使用Delphi自己的內存拷貝函數Move()時,程序總是會訪問到非法地址,所以只好改為用API函數CopyMemory()了。
  
  另外,需要實現的函數還有:
  function TFastFileStream.GetSize():Int64;
  begin
    result:=FSize;
  end;

  function TFastFileStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
  begin
    case Ord(Origin) of
      soFromBeginning: FPosition := Offset;
      soFromCurrent: Inc(FPosition, Offset);
      soFromEnd: FPosition := FSize + Offset;
    end;
    Result := FPosition;
  end;
  
這樣,一套完整的文件讀取機制就有了。
  
  由於復雜度的關系,我沒有實現文件保存機制,感興趣的朋友請自己實現吧。
  
  接下去,需要解決的是如何將目前用到的兩個Stream的復制操作進行優化,開始想到的辦法是,建立一個新的Stream類,它在從別的Stream復制出數據時,不新開內存,而是將內部的內存指針指向源Stream內的數據塊中的某一段,但是這樣一來,這個Stream類只有在源Stream的生存期內才可用,關系變得似乎有些混亂了。
  
  後來,忽然又想到另一個辦法,其實對於外部類來說(即我用到的文件型數據庫組件),它只是使用Read(),Seek()等方法來訪問數據的,那麼我只要用一些欺騙的方法,讓內部類返回給外部的只是其內部數據中的某一段就可以了。
  對於我的程序來說,在找到我要的數據的位置後,對其設置一個虛擬的數據范圍,在以後的外部訪問時,都返回該虛擬數據范圍內的數據。這樣一來,只需要在原TFastFileStream的基礎上進行一定的改造就可以了。
  
  procedure TFastFileStream.SetUseableMemory(const StartPos:Int64;const Size:Int64);
  begin
    FUseableStartPos:=StartPos;
    FSize:=Size;
  end;
  
  function TFastFileStream.Read(var Buffer;Count:LongInt):LongInt;
  begin
    ...
        CopyMemory(Pointer(@Buffer),Pointer(LongInt(FMemory)+FUseableStartPos+FPosition),Result);
    ...
  end;
  

  好了,到此為止改造就結束了,最後換上這個新寫的FileStream類,一試,速度果然是驚人的快啊,原來打開一個近30MB的文件,使用兩個Stream類,需要約40秒,改成新的TFastFileStream後,只需要一個類就搞定,時間在5秒以內,哈哈,果然爽阿!
  
  如果需要這個類的完整代碼,可以寫信聯系我:
  [email protected]
  

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved