背景
咋一看這算什麼傻問題。。。
按行讀取文本這是每種語言提供的基本功能,比如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.