заменить символы в файле (более быстрый метод) - PullRequest
3 голосов
/ 28 мая 2009

Мы часто заменяем нежелательные символы в файле другим "хорошим" символом.

Интерфейс:

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

Чтобы заменить все нежелательные места пробелом, который мы могли бы назвать, cleanfileASCII2 (original.txt, 32, cleaned.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;

Ответы [ 7 ]

6 голосов
/ 28 мая 2009

Проблема связана с тем, как вы обрабатываете буфер. Передача памяти - самая дорогая часть любой операции. В этом случае вы смотрите на файл побайтно. Если вы перейдете к чтению в формате blockread или буферизованному чтению, вы ощутите огромное увеличение скорости. Обратите внимание, что правильный размер буфера зависит от того, откуда вы читаете. Для сетевого файла вы обнаружите, что очень большие буферы могут быть менее эффективными из-за размера пакета TCP / IP. Даже это стало немного мутным с большими пакетами от gigE, но, как всегда, лучший результат - это его тестирование.

Я преобразовал стандартное чтение в поток файлов просто для удобства. Вы можете легко сделать то же самое с blockread. В этом случае я взял файл размером 15 МБ и проверил его в своей рутине. Для выполнения операции над локальным файлом потребовалось 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;
2 голосов
/ 28 мая 2009

Несколько улучшений:

  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 голос
/ 28 мая 2009

Буферизация - правильный способ сделать это. Я изменил ваш код, чтобы увидеть разницу:

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;
1 голос
/ 28 мая 2009

Вы можете буферизовать ваш ввод и вывод так, чтобы вы прочитали кусок массива (даже весь файл, если он не слишком большой) в массив, затем обработали массив, затем записали весь массив в выходной файл. *

В большинстве этих случаев дисковый ввод-вывод является узким местом, и если вы сможете выполнять меньше больших операций чтения вместо множества маленьких операций чтения, это будет быстрее.

0 голосов
/ 10 января 2013

Вероятно, самый простой способ будет:

  1. сделать другой файл (временный)
  2. копировать все содержимое основного файла в темп. файл (строка за строкой)
  3. определяет, когда он читает символы или слова, которые вы хотите заменить, и прекращает копирование
  4. введите ваши правки (во временный файл)
  5. продолжить и завершить копирование основного файла во временный файл
  6. перезаписать (удалить содержимое) базового файла
  7. копировать строки из временного файла в основной файл
  8. СДЕЛАНО!

проголосуйте за этот пост +1, если это помогло, пожалуйста

0 голосов
/ 28 мая 2009

Не пытайтесь оптимизировать, не зная где.

Вы должны использовать Sampling Profiler (delphitools.info), чтобы узнать, где узкое место. Это просто в использовании.

Предварительно вычислить преобразование vgood chr перед циклом.

Кроме того, вам не нужны некоторые преобразования: Ord () и Chr (). Всегда используйте переменную 'Ch'.

if not (ch in [#10, #13, #32..#127]) then
0 голосов
/ 28 мая 2009

Я сделал это таким образом, гарантируя, что файловый ввод-вывод будет выполнен за один проход до обработки. Код может быть использован для обновления юникода, но он справляется с неприятными текстовыми символами, такими как нули, и дает вам возможность 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;
...