2012-11-09 18 views
3

我軟處理傳入的字符串(來自Telnet或HTTP或...),並且我必須用Delphi XE2編寫文本文件才能處理字符串。由於有時字符串可能會導致程序崩潰,因此我需要確保在我的文件中包含字符串。所以我打開/關閉每個傳入的字符串的文件,我有一些性能問題。通常(對於我的代碼測試)8秒文本文件在德爾福的書寫表演

我的代碼在這裏,有沒有辦法來改善保持函數的perfs? (用於測試只需創建一個帶按鈕的表單:Button1,帶有OnClick事件& a Label:lbl1)。

Procedure AddToFile(Source: string; FileName :String); 
var 
    FText : Text; 
    TmpBuf: array[word] of byte; 
Begin 
    {$I-} 
    AssignFile(FText, FileName); 
    Append(FText); 
    SetTextBuf(FText, TmpBuf); 
    Writeln(FText, Source); 
    CloseFile(FText); 
    {$I+} 
end; 

procedure initF(FileName : string); 
Var FText : text; 
begin 
    {$I-} 
    if FileExists(FileName) then DeleteFile(FileName); 
    AssignFile(FText, FileName); 
    ReWrite(FText); 
    CloseFile(FText); 
    {$I+} 
end; 

procedure TForm1.Button1Click(Sender: TObject); 
var tTime : TDateTime; 
    iBcl : Integer; 
    FileName : string; 
begin 
    FileName := 'c:\Test.txt'; 
    lbl1.Caption := 'Go->' + FileName; lbl1.Refresh; 
    initF(FileName); 
    tTime := Now; 
    For iBcl := 0 to 2000 do 
    AddToFile(IntToStr(ibcl) + ' ' + 'lkjlkjlkjlkjlkjlkjlkj' , FileName); 
    lbl1.Caption := FormatDateTime('sss:zzz',Now-tTime); 
end; 
+2

打開/關閉文件是一個耗時的操作。爲什麼不打開它一次,並將文件指針保存在內存中?也可能在文本文件的使用上存在滯後性,嘗試使用TFileStream並通過字節塊寫入數據。要確保數據被推送到文件,使用FileStream.Flush –

+0

你應該保持打開文件,一旦你已經初始化它,或者傳遞FText變量或保持它作爲一個全局變量... – ComputerSaysNo

+0

在我看來,你正在治療一種症狀不是原因...找出崩潰發生的地方,試着......除了它周圍的塊,並處理關閉那裏的文件 - 然後重新提出例外,如果你死在那一點。 – Despatcher

回答

13

使用自動緩衝的TStreamWriter,並且可以自動處理將其緩衝區刷新爲TFileStream。它還允許您選擇追加到現有文件(如果需要),爲Unicode支持設置字符編碼,並允許您在其各種重載Create構造函數中設置不同的緩衝區大小(默認值爲1024字節或1K)。

(請注意,沖洗TStreamWriter只有TStreamBuffer的內容寫入TFileStream;它不刷新操作系統的文件系統緩存,所以文件實際上並未寫入磁盤,直到TFileStream被釋放。)

不要每次都創建StreamWriter;剛剛創建,一旦打開它,並在年底關閉它:

function InitLog(const FileName: string): TStreamWriter; 
begin 
    Result := TStreamWriter.Create(FileName, True); 
    Result.AutoFlush := True;   // Flush automatically after write 
    Result.NewLine := sLineBreak;  // Use system line breaks 
end; 

procedure CloseLog(const StreamWriter: TStreamWriter); 
begin 
    StreamWriter.Free; 
end; 

procedure TForm1.Button1Click(Sender: TObject); 
var 
    tTime : TDateTime; 
    iBcl : Integer; 
    LogSW: TStreamWriter; 
    FileName: TFileName; 
begin 
    FileName := 'c:\Test.txt'; 
    LogSW := InitLog(FileName); 
    try 
    lbl1.Caption := 'Go->' + FileName; 
    lbl1.Refresh; 
    tTime := Now; 

    For iBcl := 0 to 2000 do 
     LogSW.WriteLine(IntToStr(ibcl) + ' ' + 'lkjlkjlkjlkjlkjlkjlkj'); 

    lbl1.Caption := FormatDateTime('sss:zzz',Now - tTime); 
    finally 
    CloseLog(LogSW); 
    end; 
end; 
+0

'TStreamWriter'不會刷新OS文件緩衝區;如果需要性能,它也不允許設置自己的緩衝區大小。 – kludg

+1

@Serg:它*不允許設置它自己的緩衝區大小。檢查文件;它是通過使用重載構造函數的兩個版本中的一個來完成的(我在上面的答案中提到過)。請參閱鏈接的文檔。它也會刷新操作系統緩衝區 - 請參閱「AutoFlush」上的文檔。也許你習慣於舊版本的StreamWriter? –

+0

對不起,錯過了重載的構造函數。忘記了我所說的緩衝區大小。 :) – kludg

3

而是重新打開文件保存在磁盤上的關鍵數據,你可以使用FlushFileBuffers功能或通過調用CreateFile功能與FILE_FLAG_NO_BUFFERINGFILE_FLAG_WRITE_THROUGH標誌打開無緩衝I/O的文件(見Remarks部分在第一鏈接)。

+0

「FILE_FLAG_NO_BUFFERING」的對齊要求意味着它不是很實用。 –

+0

@DavidHeffernan - 如果你自己緩衝,這是實用的;數據寫入磁盤或寫入緩衝區。 – kludg

+0

這是不太實際的一點。必須編寫該緩衝代碼。也可以使用'FILE_FLAG_WRITE_THROUGH'或'FlushFileBuffers'。 –

2

看來你的問題是,你需要刷新每次寫操作之後的緩存,這樣,如果你的應用程序崩潰也不會丟失數據。

雖然我確定這裏的其他答案非常好,但您無需對代碼進行如此大的更改。所有你需要做的是在每次寫入之後調用Flush(FText)

const 
    // 10 million tests 
    NumberOfTests = 1000000; 

    // Open and close with each write:  19.250 seconds 

    // Open once, and flush after each write: 5.686 seconds 

    // Open once, don't flush     0.439 seconds 

var 
    FText : Text; 
    TmpBuf: array[word] of byte; 

procedure initF(FileName : string); 
begin 
    {$I-} 
    if FileExists(FileName) then DeleteFile(FileName); 
    AssignFile(FText, FileName); 
    ReWrite(FText); 
    SetTextBuf(FText, TmpBuf); 
    {$I+} 
end; 

procedure CloseTheFile; 
begin 
    CloseFile(FText); 
end; 

Procedure AddToFile(Source: string); 
Begin 
    {$I-} 
    Writeln(FText, Source); 

    // flush the cache after each write so that data will be written 
    // even if program crashes. 
    flush (fText);    // <<<==== Flush the Cache after each write 

    {$I+} 
end; 

procedure TForm1.Button1Click(Sender: TObject); 
var tTime : TDateTime; 
    iBcl : Integer; 
    FileName : string; 
begin 
    FileName := 'c:\Test.txt'; 
    lbl1.Caption := 'Go->' + FileName; lbl1.Refresh; 
    initF(FileName); 

    // put file close in a try/finally block to ensure file is closed 
    // even if an exception is raised. 
    try 

    tTime := Now; 
    For iBcl := 0 to NumberOfTests-1 do 
     AddToFile(IntToStr(ibcl) + ' ' + 'lkjlkjlkjlkjlkjlkjlkj'); 
    lbl1.Caption := FormatDateTime('sss:zzz',Now-tTime); 

    finally 
    CloseTheFile; 
    end; 
end; 
+0

順便說一下,'Rewrite'會清空文件,如果它已經存在,所以不需要先刪除文件。 –

1

由於某種原因,從一個文本文件,並寫入文本輸出文件的簡單閱讀中,我發現文本文件WriteLn仍然是最快的方法。

AssignFile(t,'c:\a\in.csv'); 
    Reset(t); 
    AssignFile(outt,'c:\a\out.csv'); 
    ReWrite(outt); 
    while not eof(t) do 
    begin 
    Readln(t,x); 
    WriteLn(outt, x); //27 sec, using LogSW.WriteLine(outx) takes 54 sec 

//半GB的文件用了27秒,上面的代碼,使用從TStreamWriter例如通過馬亭提供了54秒:○

+0

沒有真正相關但僅供參考,C#中的相同代碼需要14秒。 StreamReader sr = new StreamReader(location +「\\」+ singleFilename); StreamWriter sw = new StreamWriter(outputFolder +「\\」+ singleFilename); –