2009-05-28 6 views
3

Nous remplaçons souvent les caractères non désirables dans un fichier avec un autre caractère "bon".remplacer les caractères dans un fichier (méthode plus rapide)

L'interface est:

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

Pour remplacer tous les non-desirables avec un espace que nous pourrions appeler, cleanfileASCII2 (original.txt, 32, cleaned.txt)

Le problème est que cette prend un temps assez long. Y at-il une meilleure façon de le faire que le montre?

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; 

Répondre

1

Vous pouvez tamponner votre entrée et de sortie de sorte que vous lisez un morceau de personnages (même le fichier entier, si ce n'est pas trop grand) dans un tableau, puis de traiter le tableau, puis écrire tout le tableau à la sortie fichier.

Dans la plupart des cas, le disque IO est le goulot d'étranglement, et si vous pouvez le faire moins grand lit au lieu de plusieurs petites lectures, ce sera plus rapide.

0

Je l'ai fait de cette façon, faire en sorte que le fichier E/S est fait en une seule fois avant le traitement. Le code pourrait faire avec la mise à jour pour unicode, mais il gère les caractères de texte méchants tels que les valeurs NULL et vous donne une capacité 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

Et si vous remplacez le '' non CharInSet' par (P^<> # 10) et (P^<> # 13) 'vous aurez une boucle beaucoup plus rapide. CharInSet est en ligne mais cela ne change rien. Cela rend le compilateur incapable de générer du code optimal. –

+0

Ou 'pas P^dans [# 10, # 13]', ce qui est aussi beaucoup plus rapide. –

0

Ne pas essayer d'optimiser sans savoir où.

Vous shoud utiliser pour savoir où est le goulot d'étranglement le Générateur de profils d'échantillonnage (delphitools.info). C'est facile à utiliser.

Précalculer la vgood de conversion de chr, avant la boucle.

En outre, vous n'avez pas besoin des conversions: Ord() et Chr(). Utilisez toujours la variable 'Ch'.

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

Si vous suivez votre propre conseil, vous constaterez probablement que le pré-calcul du vGood ne fera pas beaucoup de différence (-: –

+0

Seulement si le paramètre vgood était un char: o) –

1

La mise en mémoire tampon est la méthode correcte pour cela. J'ai modifié votre code pour voir la différence:

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

Plusieurs améliorations:

  1. Tampon les données, lu 2k ou 16k ou des blocs de taille similaire
  2. Utilisez une table de recherche

voici un coup de couteau, qui n'a pas été testé (pas compilateur devant moi en ce moment):

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 pour le tampon mais je ne m'attends pas la recherche pour faire une différence significative. –

+0

+1 Henk. Aussi, pour Lasse: Vous pouvez changer l'initialisation de votre table à trois lignes et une seule boucle (pas de formatage disponible dans les commentaires): FillChar (table, sizeof (table), # 32); pour c: = # 32 à # 127 faire table [c]: = c; table [# 10]: = # 10; tableau [# 13]: = # 13; –

5

Le problème est lié à la façon dont vous traitez le tampon. Les transferts de mémoire sont la partie la plus coûteuse de toute opération. Dans ce cas, vous regardez le fichier octet par octet. En changeant à une lecture bloquée ou tamponnée, vous réaliserez une énorme augmentation de la vitesse. Notez que la taille de la mémoire tampon varie en fonction de l'endroit où vous lisez. Pour un fichier en réseau, vous remarquerez que des tampons extrêmement volumineux peuvent être moins efficaces en raison de la taille de paquet imposée par TCP/IP. Même cela est devenu un peu trouble avec de gros paquets de gigE mais, comme toujours, le meilleur résultat est de le comparer.

Je suis passé de lectures standard à un flux de fichiers juste pour plus de commodité. Vous pourriez facilement faire la même chose avec un blockread. Dans ce cas, j'ai pris un fichier de 15 Mo et l'ai exécuté dans votre routine. Il a fallu 131 478 ms pour effectuer l'opération sur un fichier local. Avec le tampon 1024, il a fallu 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

Probablement la méthode la plus simple serait:

  1. faire un autre fichier (temporaire)
  2. copie tout le contenu du fichier de base à la température. fichier (ligne après ligne)
  3. détecter quand il lit les caractères ou les mots que vous souhaitez remplacer et arrêter la copie
  4. entrez votre modifier (dans le fichier temporaire.)
  5. continuer et terminer la copie de base de fichier temporaire
  6. réécrire (supprimer le contenu du) fichier de base
  7. copier des lignes du fichier temporaire vers le fichier de base
  8. FAIT!

vote ce poste +1 si elle a aidé s'il vous plaît

Questions connexes