提升老版本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.

Guess you like

Origin blog.csdn.net/ddrfan/article/details/104844512