2009-05-28 44 views
3

我們經常用另一個「好」字符替換文件中不需要的字符。替換文件中的字符(更快的方法)

接口爲:

procedure cleanfileASCII2(vfilename: string; vgood: integer; voutfilename: string); 

要使用我們可以稱之爲一個空間, cleanfileASCII2(original.txt,32,cleaned.txt)

的問題是,這種替換所有非desirables需要相當長的時間。有沒有 更好的方式來做到這一點?

procedure cleanfileASCII2(vfilename: string; vgood: integer; voutfilename: 
string); 
var 
    F1, F2: file of char; 
    Ch: Char; 
    tempfilename: string; 
    i,n,dex: integer; 
begin 
    //original 
    AssignFile(F1, vfilename); 
    Reset(F1); 
    //outputfile 
    AssignFile(F2,voutfilename); 
    Rewrite(F2); 
     while not Eof(F1) do 
     begin 
     Read(F1, Ch); 
     // 
      n:=ord(ch); 
      if ((n<32)or(n>127))and (not(n in [10,13])) then 
      begin // bad char 
       if vgood<> -1 then 
       begin 
       ch:=chr(vgood); 
       Write(F2, Ch); 
       end 
      end 
      else //good char 
      Write(F2, Ch); 
     end; 
    CloseFile(F2); 
    CloseFile(F1); 
end; 

回答

1

所以你讀的字符塊(甚至是整個文件,如果它不是太大)到一個數組你可以緩衝自己的輸入和輸出,然後處理數組,然後寫入整個陣列的輸出文件。

在大多數情況下,磁盤IO是瓶頸,如果你可以做更少的大讀取而不是許多小讀取,它會更快。

0

我這樣做,確保文件I/O在處理之前一次完成。該代碼可以爲unicode進行更新,但它可以處理惡意文本字符,例如空值,併爲您提供TStrings功能。 Bri

procedure TextStringToStringsAA(AStrings : TStrings; const AStr: Ansistring); 
// A better routine than the stream 'SetTextStr'. 
// Nulls (#0) which might be in the file e.g. from corruption in log files 
// do not terminate the reading process. 
var 
    P, Start, VeryEnd: PansiChar; 
    S: ansistring; 
begin 
    AStrings.BeginUpdate; 
    try 
    AStrings.Clear; 

    P := Pansichar(AStr); 
    VeryEnd := P + Length(AStr); 

    if P <> nil then 
     while P < VeryEnd do 
     begin 
     Start := P; 
     while (P < VeryEnd) and not CharInSet(P^, [#10, #13]) do 
     Inc(P); 
     SetString(S, Start, P - Start); 
     AStrings.Add(string(S)); 
     if P^ = #13 then Inc(P); 
     if P^ = #10 then Inc(P); 
     end; 
    finally 
    AStrings.EndUpdate; 
    end; 
end; 


procedure TextStreamToStrings(AStream : TStream; AStrings : TStrings); 
// An alternative to AStream.LoadFromStream 
// Nulls (#0) which might be in the file e.g. from corruption in log files 
// do not terminate the reading process. 
var 
    Size : Integer; 
    S : Ansistring; 
begin 
    AStrings.BeginUpdate; 
    try 
    // Make a big string with all of the text 
    Size := AStream.Size - AStream.Position; 
    SetString(S, nil, Size); 
    AStream.Read(Pointer(S)^, Size); 

    // Parse it 
    TextStringToStringsAA(AStrings, S); 
    finally 
    AStrings.EndUpdate; 
    end; 
end; 

procedure LoadStringsFromFile(AStrings : TStrings; const AFileName : string); 
// Loads this strings from a text file 
// Nulls (#0) which might be in the file e.g. from corruption in log files 
// do not terminate the reading process. 
var 
    ST : TFileStream; 
begin 
    ST := TFileStream.Create(AFileName, fmOpenRead + fmShareDenyNone); 
    // No attempt is made to prevent other applications from reading from or writing to the file. 
    try 
    ST.Position := 0; 
    AStrings.BeginUpdate; 
    try 
     TextStreamToStrings(ST, AStrings); 
    finally 
     AStrings.EndUpdate; 
    end; 

    finally 
    ST.Free; 
    end; 
end; 
+1

如果你更換`不是`CharInSet`(P^<>#10)和(P^<>#13)`你將有一個更快的循環。 CharInSet是內聯的,但不會改變任何內容。它使編譯器無法生成最佳代碼。 – 2009-05-28 15:51:19

+0

或[#10,#13]中的'not P ^',這也是非常快的。 – 2009-05-28 20:04:49

0

不要試圖優化不知道在哪裏。

你應該使用Sampling Profiler(delphitools.info)來了解瓶頸在哪裏。它很容易使用。

在循環之前預先計算vgood chr轉換。

此外,您不需要一些轉換:Ord()和Chr()。總是使用'​​Ch'變量。

if not (ch in [#10, #13, #32..#127]) then 
+0

如果你遵循自己的建議,你會發現預計算vGood不會有很大的區別( - : – 2009-05-28 16:26:24

1

緩衝是正確的方式來做到這一點。我修改了代碼,看到了差距:

procedure cleanfileASCII2(vfilename: string; vgood: integer; voutfilename: 
string); 
var 
    F1, F2: file; 
    NumRead, NumWritten: Integer; 
    Buf: array[1..2048] of Char; 
    Ch: Char; 
    i, n: integer; 
begin 
    AssignFile(F1, vfilename); 
    Reset(F1, 1); // Record size = 1 
    AssignFile(F2, voutfilename); 
    Rewrite(F2, 1); // Record size = 1 
    repeat 
     BlockRead(F1, Buf, SizeOf(Buf), NumRead); 
     for i := 1 to NumRead do 
     begin 
     Ch := Buf[i]; 
     // 
     n := ord(ch); 
     if ((n<32)or(n>127))and (not(n in [10,13])) then 
     begin // bad char 
     if vgood <> -1 then 
     begin 
      ch := chr(vgood); 
      Buf[i] := Ch; 
     end 
     //else //good char 
     //Write(F2, Ch); 
     end; 
     end; 
     BlockWrite(F2, Buf, NumRead, NumWritten); 
    until (NumRead = 0) or (NumWritten <> NumRead); 
    CloseFile(F1); 
    CloseFile(F2); 
end; 
2

幾點改進:

  1. 緩衝區中的數據,讀取2K或16K或類似大小的塊
  2. 使用查表

這是一個刺,這是未經測試(目前沒有編譯器在我面前):

procedure cleanfileASCII2(vfilename: string; vgood: integer; voutfilename: string); 
var 
    f1, f2: File; 
    table: array[Char] of Char; 
    index, inBuffer: Integer; 
    buffer: array[0..2047] of Char; 
    c: Char; 
begin 
    for c := #0 to #31 do 
     table[c] := ' '; 
    for c := #32 to #127 do 
     table[c] := c; 
    for c := #128 to #255 do 
     table[c] := ' '; 
    table[#10] := #10; // exception to spaces <32 
    table[#13] := #13; // exception to spaces <32 

    AssignFile(F1, vfilename); 
    Reset(F1, 1); 
    AssignFile(F2,voutfilename); 
    Rewrite(F2, 1); 
    while not Eof(F1) do 
    begin 
     BlockRead(f1, buffer, SizeOf(buffer), inBuffer); 
     for index := 0 to inBuffer - 1 do 
      buffer[index] := table[buffer[index]]; 
     BlockWrite(f2, buffer, inBuffer); 
    end; 
    Close(f2); 
    Close(f1); 
end; 
5

問題與您如何處理緩衝區有關。內存傳輸是任何操作中最昂貴的部分。在這種情況下,您正在逐字節地查看文件。通過更改爲塊閱讀或緩衝讀取,您將意識到速度的巨大提高。請注意,正確的緩衝區大小取決於您正在閱讀的位置。對於聯網文件,由於TCP/IP規定的數據包大小,您會發現非常大的緩衝區可能效率較低。即使這對於來自gigE的大數據包也有點模糊,但是一如既往,最好的結果就是對它進行基準測試。

爲了方便,我將標準讀取轉換爲文件流。你可以輕鬆地做一個blockread同樣的事情。在這種情況下,我拿了一個15MB的文件,並通過你的例程。它花了131,478ms在本地文件上執行操作。 1024緩衝區花費了258ms。

procedure cleanfileASCII3(vfilename: string; vgood: integer; voutfilename:string); 
const bufsize=1023; 
var 
    inFS, outFS:TFileStream; 
    buffer: array[0..bufsize] of byte; 
    readSize:integer; 
    tempfilename: string; 
    i: integer; 
begin 
    if not FileExists(vFileName) then exit; 

    inFS:=TFileStream.Create(vFileName,fmOpenRead); 
    inFS.Position:=0; 
    outFS:=TFileStream.Create(vOutFileName,fmCreate); 
    while not (inFS.Position>=inFS.Size) do 
     begin 
     readSize:=inFS.Read(buffer,sizeof(buffer)); 
     for I := 0 to readSize-1 do 
      begin 
      n:=buffer[i]; 
      if ((n<32)or(n>127)) and (not(n in [10,13])) and (vgood<>-1) then 
      buffer[i]:=vgood; 
      end; 
     outFS.Write(buffer,readSize); 
     end; 
    inFS.Free; 
    outFS.Free; 
end; 
0

也許最簡單的方法是:

  1. 再拍文件(臨時)
  2. 副本基本文件的所有內容複製到temp。文件(行後線)
  3. 檢測時,它會讀取你要替換,並停止複製
  4. 輸入您的編輯(以臨時。文件)
  5. 繼續並完成複製基本到臨時文件字符或單詞
  6. 重寫(刪除內容)基本文件
  7. 將臨時文件複製到基本文件
  8. 完成!

投票這個職位+1,如果它幫助請