【问题标题】:replace characters in a file (faster method)替换文件中的字符(更快的方法)
【发布时间】:2010-10-29 14:50:33
【问题描述】:

我们经常用另一个“好”字符替换文件中不需要的字符。

界面是:

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

用我们可以称之为的空格替换所有不受欢迎的, cleanfileASCII2(original.txt, 32, clean.txt)

问题是这需要相当长的时间。有没有 比显示的更好的方法?

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;

【问题讨论】:

    标签: delphi file replace ascii


    【解决方案1】:

    可能最简单的方法是:

    1. 制作另一个文件(临时)
    2. 将基本文件的所有内容复制到临时文件中。文件(一行接一行)
    3. 检测何时读取要替换的字符或单词并停止复制
    4. 输入您的编辑(到临时文件)
    5. 继续并完成将基本文件复制到临时文件
    6. 重写(删除)基本文件的内容
    7. 将行从临时文件复制到基本文件
    8. 完成!

    如果对这篇文章有帮助,请投票+1

    【讨论】:

      【解决方案2】:

      问题与您处理缓冲区的方式有关。内存传输是任何操作中最昂贵的部分。在这种情况下,您正在逐字节查看文件。通过更改为块读取或缓冲读取,您将实现速度的巨大提升。请注意,正确的缓冲区大小会根据您读取的位置而有所不同。对于网络文件,您会发现由于 TCP/IP 强加的数据包大小,非常大的缓冲区可能效率较低。即使这对于来自 gigE 的大数据包也变得有点模糊,但一如既往,最好的结果是对其进行基准测试。

      为了方便,我将标准读取转换为文件流。您可以使用块读取轻松地做同样的事情。在这种情况下,我拿了一个 15MB 的文件并通过你的例程运行它。对本地文件执行操作花费了 131,478 毫秒。使用 1024 缓冲区,需要 258 毫秒。

      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;
      

      【讨论】:

        【解决方案3】:

        多项改进:

        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;
        

        【讨论】:

        • +1 用于缓冲,但我不希望查找产生任何显着差异。
        • +1 亨克。此外,对于 Lasse:您可以将表初始化更改为三行和一个循环(cmets 中没有可用的格式): FillChar(table, sizeof(table), #32);对于 c := #32 到 #127 做 table[c] := c;表[#10] := #10;表[#13] := #13;
        【解决方案4】:

        缓冲是做到这一点的正确方法。我修改了您的代码以查看差异:

        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;
        

        【讨论】:

          【解决方案5】:

          不要在不知道哪里的情况下尝试优化。

          您应该使用采样分析器 (delphitools.info) 来了解瓶颈在哪里。它易于使用。

          在循环之前预先计算 vgood chr 转换。

          另外,您不需要一些转换:Ord() 和 Chr()。始终使用“Ch”变量。

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

          【讨论】:

          • 如果您遵循自己的建议,您可能会发现预先计算 vGood 并不会产生太大影响(-:
          【解决方案6】:

          我是这样做的,确保文件 I/O 在处理之前一次性完成。该代码可以用于更新 unicode,但它可以处理令人讨厌的文本字符,例如 null,并为您提供 TStrings 功能。 布里

          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;
          

          【讨论】:

          • 如果您将not CharInSet 替换为(P^ &lt;&gt; #10) and (P^ &lt;&gt; #13),您将获得更快的循环。 CharInSet 是内联的,但这不会改变任何东西。导致编译器无法生成最优代码。
          • 或者'not P^ in [#10, #13]',也快很多。
          【解决方案7】:

          您可以缓冲您的输入和输出,以便将一大块字符(甚至整个文件,如果它不是太大的话)读入一个数组,然后处理该数组,然后将整个数组写入输出文件。

          在大多数情况下,磁盘 IO 是瓶颈,如果您可以执行较少的大读取而不是许多小读取,则速度会更快。

          【讨论】:

            猜你喜欢
            • 2016-11-14
            • 1970-01-01
            • 2020-12-31
            • 1970-01-01
            • 2023-03-29
            • 1970-01-01
            • 2013-09-21
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多