提升老版本Delphi按行讀取文本文件的效率

背景

咋一看這算什麼傻問題。。。
按行讀取文本這是每種語言提供的基本功能,比如Java可以這樣:

aLine = BufferedReader.readLine();

而C語言可以用:

fgets(someBuff, sizeof(someBuff), somefile_of_fopen);

Delphi一般都是:

Readln(someTEXTFILE,aLine);

當然我們也可以用流的方式讀取。

問題

太慢!

20年前Delphi是最快的,當時測試至少比標準C++快。
20年前,馬雲還在騎自行車,網易買沒有養豬,騰訊只有一個產品OICQ……

現在計算機系統性能次方的增加,特別是高速SSD普及後。
這時候再測試Delphi7,標準C++(好像是11),Java8,文本讀取性能,可以發現Delphi已經從20年前最快,變成最慢的了。。。

而很多老模塊都是delphi寫的,很難全部移植到別的語言比如java。

分析

經過測試整塊整塊數據讀取還是比較快的,至少和磁盤性能成比例。
但是文本按行讀取,無論是用流,還是文本文件,Delphi都很慢。

猜測是內部處理的時候,緩存留太小了。

勉強提升的辦法

設置緩存

//準備一塊緩存,別太大了。
someBuffer: array[1..8*1024] of byte;

//在準備讀寫文件前設置文本緩存
reset(someTEXTFILE);
SetTextBuf(someTEXTFILE,someBuffer);

這樣會稍微快一點點,但是還不夠。

自己寫

因爲寫入沒法提升,就在讀取上想辦法,總體思路:

  • 用文件流整塊讀取
  • 通過判斷換行位置將文本分成多行(考慮Win/Linux區別,還得處理回車)。
  • 多行賦給數組,用得時候取出來(也可以不這樣,用的時候直接讀,但是會稍慢一點點)。
  • 按實際情況拼接跨塊的文本。

最後結果實際也快了一點點,感覺還不夠,但是不知道怎麼提升了-__-
PS:別太在意public。
額,還有宏定義是爲了考慮到新版。

{
//  by:若苗瞬
//  使用 自定義函數將固定最大長度的PAnsiChar數組元素指向替換了0隔開的緩衝區數據。
//  ReadLN時,返回單條String(AnsiString(PAnsiChar[x]))。
}

unit UnShionTextFileReader_A;

interface
uses
  {$IFDEF MSWINDOWS}
  Windows,
  {$ENDIF }
  SysUtils,Classes
  {$IFDEF VER330}
  ,System.AnsiStrings
  {$DEFINE NewRADXE}
  {$ENDIF}
  ;

const
  ShionTextFileReadBufferSize=16*1024;

type
	TShionTextFileReader=class
      private
          Afile:TFileStream;
          fFileName:String;
          fMode:Word;
          ReadBuf: array[1..ShionTextFileReadBufferSize+1] of AnsiChar;
          i:Cardinal;
          ReadPAnsiChar:PAnsiChar;
          ReadOutCount:Cardinal;
          //TmpList:TStringList;
          TmpList: array[0..ShionTextFileReadBufferSize-1] of PAnsiChar;
          TmpListCount:Cardinal;
          TmpEnd,TmpEndNext:Boolean;
          TmpLast:PAnsiChar;
          TmpLastBuf:array[1..ShionTextFileReadBufferSize*2+1] of AnsiChar;
      public
          MyEOF: boolean;
      public
          constructor Create(FileName:String;Mode: Word);
          destructor Destroy;override;
          procedure Reset();
          procedure SplitReadBuftoList();
          function ReadLN(var aLine:AnsiString):boolean;
    end;

implementation


constructor TShionTextFileReader.Create(FileName:String;Mode: Word);
begin
  fFileName:=FileName;
  fMode:=Mode;
  Afile:=TFileStream.Create(fFileName,fMode);
  ReadPAnsiChar:=@ReadBuf[1];
  TmpLast:=@TmpLastBuf[1];
  Reset();
end;

destructor TShionTextFileReader.Destroy;
begin
  inherited;
  FreeAndNil(Afile);
end;

procedure TShionTextFileReader.Reset();
begin
  i:=0;
  TmpEnd:=true;
  Afile.Seek(0,0);
  ReadOutCount:=0;
  TmpListCount:=0;
  MyEOF:=false;
end;

procedure TShionTextFileReader.SplitReadBuftoList();
var
  j:Cardinal;
begin
  j:=1;
  TmpListCount:=1;
  TmpList[0]:=@ReadBuf[1];
  while j<=ReadOutCount do
  begin
    while j<=ReadOutCount do
    begin
      if (ReadBuf[j]=#13) and (ReadBuf[j+1]=#10) then
      begin
        ReadBuf[j]:=#0;
        ReadBuf[j+1]:=#0;
        j:=j+2;
        break;
      end
      else if (ReadBuf[j]=#10) then
      begin
        ReadBuf[j]:=#0;
        Inc(j);
        break;
      end;
      Inc(j);
    end;
    if j<=ReadOutCount then
    begin
      TmpList[TmpListCount]:=@ReadBuf[j];
      Inc(TmpListCount);
    end;
  end;
  if ReadBuf[ReadOutCount]=#13 then
    ReadBuf[ReadOutCount]:=#0;
end;

function TShionTextFileReader.ReadLN(var aLine:AnsiString):boolean;
begin
  {$IFDEF NewRADXE}
  ;
  {$ELSE}
  Result:=false;
  {$ENDIF}
  while true do
  begin
    if (i>=TmpListCount) then
    begin
      if not MyEOF then
      begin
        i:=0;
        ReadOutCount:=Afile.Read(ReadBuf,ShionTextFileReadBufferSize);

        if ReadOutCount=0 then
        begin
          MyEOF:=true;
          result:=false;
          break;
        end;

        if ReadOutCount<>ShionTextFileReadBufferSize then
          MyEOF:=true;

        ReadBuf[ReadOutCount+1]:=#0;
        TmpEndNext:=ReadBuf[ReadOutCount]=#10;
        SplitReadBuftoList();
      end
      else
      begin
        if not TmpEnd then
        begin
          aLine:=TmpLast;
          TmpEnd:=true;
          result:=true;
          break;
        end
        else
        begin
          result:=false;
          break;
        end;
      end;
    end;

    if i=0 then
    begin
      if TmpEnd then
      begin
        aLine:=TmpList[i];
      end
      else
      begin
        {$IFDEF NewRADXE}
        aLine:=System.AnsiStrings.StrCat(TmpLast,TmpList[i]);
        {$ELSE}
        aLine:=StrCat(TmpLast,TmpList[i]);
        {$ENDIF}
        TmpEnd:=true;
      end;
      result:=true;
      Inc(i);
      break;
    end
    else if i<TmpListCount-1 then
    begin
      aLine:=TmpList[i];
      result:=true;
      Inc(i);
      break;
    end
    else
    begin
      TmpEnd:=TmpEndNext;
      if TmpEndNext then
      begin
        aLine:=TmpList[i];
        result:=true;
        Inc(i);
        break;
      end
      else
      begin
        {$IFDEF NewRADXE}
        System.AnsiStrings.StrCopy(TmpLast,TmpList[i]);
        {$ELSE}
        StrCopy(TmpLast,TmpList[i]);
        {$ENDIF}
        Inc(i);
        continue;
      end;
    end;
  end;
end;

end.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章