Avatar billede js_delphi Nybegynder
23. juni 2009 - 10:16 Der er 14 kommentarer og
1 løsning

StringReplace for langsom. Hvad er hurtigere?

Hej,

Jeg har en .txt fil paa ca. 3 MB.
Filen indeholder forskellige tegn, som skal erstattes af andre tegn (f.eks. skal '_' erstattes af '').

Med StringReplace er det som om programmet crasher; i hvert fald tager det laengere end 1 time (indtil videre) at komme igennem filen, hvilket i mit tilfaelde er totalt uacceptabelt.

Hvis jeg forsoeger at aabne filen i Editor, og erstatte tegnene her, crasher Editor ligeledes efter kort tid.

Hvilke muligheder findes der ellers til en saadan aktion?

Min PC:
1.6 GHz
2 GB RAM
Windows XP SP3

Mit testprogram:
procedure TForm1.Button1Click(Sender: TObject);
var
  SL1: TStringList;
begin
  SL1 := TStringList.Create;
  try
    SL1.LoadFromFile('D:\Temp\Test.txt');
    SL1.Text := StringReplace(SL1.Text, '_', '', [rfReplaceAll]);
    SL1.SaveToFile('D:\Temp\Test2.txt');
  finally
    SL1.Free;
  end;
end;

Paa forhaand tak!
Avatar billede snowball Novice
23. juni 2009 - 10:47 #1
Løb listen igennem i stedet for.

procedure TForm1.Button1Click(Sender: TObject);
var
  SL1: TStringList;
  i: Integer;
begin
  SL1 := TStringList.Create;
  try
    SL1.LoadFromFile('D:\Temp\Test.txt');
    For i := 0 To SL1.Count - 1 Do Begin
      SL1.Strings[i] := StringReplace(SL1.Strings[i], '_', '', [rfReplaceAll]);
    End;
    SL1.SaveToFile('D:\Temp\Test.txt');
  finally
    SL1.Free;
  end;
end;

Har testet ovenstående på en 4,2MB fil fyldt med "latinsk junk" hvor bogstavet "e" blev byttet ud med "ingenting" og det tog kun ganske få sekunder.
23. juni 2009 - 11:05 #2
HEJ,

Ville egentlig også skrive noget nær det samme som Snowball, men ingen grund til at genopdage hjulet. Det fungerer.

Havde samme problemstilling for ca 1 år siden. Løste det også ved at splitte filen op i enkeltstående linier.

Good LUCK..

Kristian

/Snestrup2000
Avatar billede js_delphi Nybegynder
23. juni 2009 - 12:38 #3
Min fil bestaar aabenbart kun af 2 (lange) linier, saa det er stort set samme resultat.
Filen er en .xml fil, som jeg har omdoebt til .txt for at udskifte ovennaevnte tegn.
Avatar billede mcb2001 Nybegynder
23. juni 2009 - 12:46 #4
er der ikke en metode til at læse et tegn af gang i delphi? skifte dette og så gemme det igen...
24. juni 2009 - 00:06 #5
Hej Igen,

Uden at være expert på feltet vil jeg tro, at den bedste måde at løse problemet på, er at du på en eller anden måde må dele den MAXI-streng op i mindre strenge.

Jeg MENER at have læst et eller andet sted (husker ikke hvor), at Delphi laver en kopi af den streng den ændrer og med en streng på 1GB (eller mere !) skal der ikke ret mange ændringer til før du får et massivt mem-overflow og prgm-krash. 

Men da XML filer (som HTML-filer) styres af TAGS -  kan du ikke dele dem op i XML-tags linier ?

Kristian

Et alternativ er at læse filen TEGN for TEGN og skifte tilsvarende (som foreslået af mcb2001).
Avatar billede mbsnet Nybegynder
26. juni 2009 - 01:38 #6
Hej, jeg bruger altid blot en pChar (pointer) til sådanne opgaver. Der behøver man ikke at kalde "length" eller bruge "for". i stedet læser man blot indtil "#0" (NULL) og så kan man lave sin egen formattering undervejs. eksempel:

const NULL = #0;
//...
var p:pChar;s:str;
//...

s:='123_123_123';
p:=pointer(s);
if p<>nil then while p^<> NULL do begin
if p^='_' then p^:=' ';//erstat underscore med mellemrum
inc(p)
end;

//mortenbs
Avatar billede mbsnet Nybegynder
26. juni 2009 - 13:48 #7
p.s. ved brug af pointer, er længden på strengen "sådan set" underordnet, da man netop er uden om "length" osv. Det mest rigtige er i virkeligheden nok at beholde det hele i et stream. Et memory stream er blot et "stream" med en indbygget pointer, som holder dataene. Så er der slet ikke behov for at bruge en string.
Avatar billede borrisholt Novice
28. juni 2009 - 09:16 #8
Vil det være muligt at se din fil ?
Avatar billede js_delphi Nybegynder
29. juni 2009 - 21:09 #9
Now we're talking...

procedure TForm1.Button1Click(Sender: TObject);
var
  p:pChar;
  SL1: TStringList;
  s: String;
  TickCnt: Integer;
begin
  SL1 := TStringList.Create;
  try
    TickCnt := GetTickCount;
    SL1.LoadFromFile('D:\Temp\Test.txt');
    s := SL1.Text;
    p:=pointer(s);
    if p<>nil then
      while p^<> NULL do
      begin
        if p^='W' then p^:='T';//erstat W med T.
        inc(p);
      end;
    SL1.Text := s;
    SL1.SaveToFile('D:\Temp\Test2.txt');
    ShowMessage(IntToStr(GetTickCount-TickCnt));
  finally
    SL1.Free;
  end;
end;

Hos mig tog det 109 ms.

Saa skal jeg bare have den til at acceptere en string, men det skulle vaere til at finde ud af.

mbsnet, laeg et svar.

Ellers tak for hjaelpen til jer andre.
Avatar billede mbsnet Nybegynder
29. juni 2009 - 22:29 #10
ok :) Men husk at der kan spares endnu mere, ved at beholde det i et stream. en tStringList læser og skriver filer via et stream i forvejen, så ved at undgå listen, springer man et helt "niveau" over...
Avatar billede borrisholt Novice
30. juni 2009 - 07:28 #11
js_delphi din løsning duer ikke til at ersatatte '_' med ''
Avatar billede mbsnet Nybegynder
30. juni 2009 - 15:09 #12
Hej jens, jeg har et par funktioner til formålet hvis det har interesse...

//mbs
Avatar billede mbsnet Nybegynder
30. juni 2009 - 15:23 #13
ps. jeg bruger selv en pointer som en "standard" variabel type (altså jeg bruger den lige så tit som et tal eller en streng). Når man har vænnet sig til det, er det faktisk lettere i mange situationer, såsom at lave case formattering eller tælle antal tegn i en tekst / lign. Derudover er det langt hurtigere.

Har sågar læst et sted, at pointere en "dying age". Det er da det værste vås at lukke ud på internet. Min mening er, at pointere er yderst relevant, for optimal software
Avatar billede borrisholt Novice
30. juni 2009 - 17:23 #14
mbsnet>>Ellers tak for tilbudet jeg har hvad jeg skal bruge i de nretning.

Hvad angår pointere så skal du lige passe på når du kommer ind i UicodeVerden, fordi hver tegn består af 1,2 eller 4 bytes. I øvrigt hvis du alligevel vil have en pointer til dine data så ladvære med at bruge TStringList til at loade filen ind med. Hvis du har 300 mb data i en fil tager den 8-900 millioner år at loade (overdrivelse fremme forståelsen) brug i stedet MapViewofFile.
Avatar billede borrisholt Novice
30. juni 2009 - 17:36 #15
Her er lidt kodt til lynhurtig læsning af filer :

unit FastFileU;

interface

uses
  SysUtils, Classes;

type
  TStringFunction = function: string of object;
  TDataKind = (dkUnknown, dkAnsi, dkUniCode);

  //loader fil til pagefile for hurtigere read/write
  TFastFile = class
  strict private
    FFileName: TFileName;
    //  FileHandle : THandle;
    FRefreshAfterAppend: Boolean;
    FReadLine: TStringFunction;
    FDataKind: TDataKind;
    MemoryStream: TMemoryStream;

    procedure SetRefreshAfterAppend(const Value: Boolean);
    procedure SetFileName(const Value: TFileName);
    procedure CloseFile;
    procedure ClearData;
    function GetDataKind: TDataKind;
    procedure InternalReadFile;
  strict protected
    FStart: PChar;
    FPos: PChar;
    FEnd: PChar;
    FDataLength: Int64;

    procedure SetData(Data: PChar; DataLength: Int64);
    function ReadLineA: string; virtual;
    function ReadLineW: string; virtual;
  public
    constructor Create;
    destructor Destroy; override;
    function ReadBuffer(ABytesToRead: Integer): pByteArray;
    function EndOfFile: Boolean;
    function AppendToFile(const Buffer: TStrings): boolean; overload;
    function AppendToFile(const Buffer: string): boolean; overload;

    property DataKind: TDataKind read GetDataKind;
    property FileName: TFileName read FFileName write SetFileName;
    property ReadLine: TStringFunction read FReadLine;
    property RefreshAfterAppend: Boolean read FRefreshAfterAppend write SetRefreshAfterAppend;

  end;

implementation

uses
  Windows, Math, WideStrUtils;

function TFastFile.AppendToFile(const Buffer: TStrings): boolean;
begin
  Result := AppendToFile(Buffer.Text);
end;

function TFastFile.AppendToFile(const Buffer: string): boolean;
var
  FileHandle: THandle;
begin
  Result := False;
  try
    FPos := FEnd;

    FileHandle := FileOpen(FileName, fmOpenReadWrite or fmShareDenyWrite);
    FileSeek(FileHandle, 0, soFromEnd);
    FileWrite(FileHandle, pointer(Buffer)^, length(Buffer));
    FileClose(FileHandle);
    if FRefreshAfterAppend then
      InternalReadFile;
    Result := True;
  except
  end;
end;

procedure TFastFile.ClearData;
begin
  FStart := nil;
  FPos := nil;
  FEnd := nil;
  FDataLength := 0;
  FDataKind := dkUnknown;
end;

procedure TFastFile.CloseFile;
begin
  if (FStart <> nil) then
    UnmapViewOfFile(FStart);

  FFileName := '';

  if Assigned(MemoryStream) then
    FreeAndNil(MemoryStream);

  ClearData;
end;

constructor TFastFile.Create;
begin
  inherited;
  FRefreshAfterAppend := False;
  ClearData;
end;

destructor TFastFile.Destroy;
begin
  CloseFile;
  inherited;
end;

function TFastFile.EndOfFile: Boolean;
begin
  Result := FPos >= FEnd;
end;

function TFastFile.GetDataKind: TDataKind;
var
  UnicodeMarker: WORD;
begin
  Result := dkAnsi;

  if FDataLength <= 1 then
    Exit;

  UnicodeMarker := (Word(FStart[0]) shl 8) or WORD(FStart[1]);

  if UnicodeMarker = $FFFE then
    Result := dkUniCode;
end;

procedure TFastFile.InternalReadFile;
var
  FileHandle: Int64;
  FileMappingHandle: THandle;
  Size: Int64;
begin
  if not FileExists(Filename) then
  begin
    FileHandle := FileCreate(FileName);
    FileClose(FileHandle);
  end;

  FileHandle := SysUtils.FileOpen(Filename, fmopenread or fmsharedenynone);

  if FileHandle = 0 then
    Exit; //error

  Size := GetFileSize(FileHandle, nil);
  if Size <= 0 then
  begin
    CloseHandle(FileHandle);
    Exit;
  end;

  FileMappingHandle := CreateFileMapping(FileHandle, nil, PAGE_READONLY, 0, 0, nil);

  if GetLastError = ERROR_ALREADY_EXISTS then
    FileMappingHandle := 0;

  if FileMappingHandle <> 0 then
    SetData(MapViewOfFile(FileMappingHandle, FILE_MAP_READ, 0, 0, 0), Size);

  if Assigned(MemoryStream) then
    FreeAndNil(MemoryStream);

  if FStart <> nil then
    FFileName := Filename;

  CloseHandle(FileMappingHandle);
  CloseHandle(FileHandle);
end;

function TFastFile.ReadBuffer(ABytesToRead: Integer): pByteArray;
begin
  ABytesToRead := Min(ABytesToRead, FEnd - FPos);
  GetMem(Result, ABytesToRead);
  Move(FPos^, Result^, ABytesToRead);
end;

function TFastFile.ReadLineA: string;
var
  P: PChar;
  Tmp, TmpA: string;
begin
  p := FPos;
  TmpA := '';

  while (not (P^ in [#10, #13])) and (P <= FEnd) do
  begin
    if p^ = #0 then
    begin
      SetString(Tmp, FPos, P - FPos);
      TmpA := TmpA + Tmp;
      Inc(P);
      FPos := P;
    end
    else
      Inc(P);
  end;

  SetString(Result, FPos, P - FPos);

  if TmpA <> '' then
    Result := TmpA + Result;

  if P^ = #13 then
    Inc(P);

  if P^ = #10 then
    Inc(P);

  FPos := P;
end;

function TFastFile.ReadLineW: string;
var
  P: PWideChar;
  Tmp, TmpA: string;
begin
  p := PWideChar(FPos);
  TmpA := '';

  while (not InOpSet(P^, [#10, #13])) and (P <= FEnd) do
  begin
    if p^ = #0 then
    begin
      Tmp := WideCharLenToString(PWideChar(FPos), P - FPos);
      TmpA := TmpA + Tmp;
      Inc(P);
      FPos := pointer(P);
    end
    else
      Inc(P);
  end;

  Result := WideCharLenToString(PWideChar(FPos), P - FPos);

  if TmpA <> '' then
    Result := TmpA + Result;

  if P^ = #13 then
    Inc(P);

  if P^ = #10 then
    Inc(P);

  FPos := pointer(P);
end;

procedure TFastFile.SetData(Data: PChar; DataLength: Int64);
begin
  ClearData;

  if (Data = nil) or (DataLength < 1) then
    Exit;

  FStart := Data;
  FPos := FStart;
  FDataLength := DataLength;
  FEnd := FStart + FDataLength;

  FDataKind := GetDataKind;

  if FDataKind = dkUniCode then
    Inc(FPos, 2);

  FReadLine := ReadLineA;

  if FDataKind = dkUniCode then
    FReadLine := ReadLineW;
end;

procedure TFastFile.SetFileName(const Value: TFileName);
begin
  CloseFile;

  FFileName := Value;

  if (Filename = '') then
    Exit;

  ForceDirectories(ExtractFilePath(FileName));
  InternalReadFile;
end;

procedure TFastFile.SetRefreshAfterAppend(const Value: Boolean);
begin
  FRefreshAfterAppend := Value;
end;

end.
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester