![]() |
#0 perfomanceschonend aus String entfernen
Hallo,
ich lade mittels folgenden Code eine Text-Datei in mein Programm, dass #0 enthält:
Delphi-Quellcode:
In "S" hab ich dann den String der Datei aber noch mit #0.
//FileStream und StringStream erzeugen
FileStream := TFileStream.Create(Datei, fmOpenRead); StringStream := TStringStream.Create(S); try //FileStream in StringStream kopieren FileStream.Position := 0; StringStream.CopyFrom(FileStream, FileStream.Size); //String aus StringStream auslesen StringStream.Position := 0; S := StringStream.ReadString(StringStream.Size); Um dieses "Zeichen" jetzt zu entfernen, nutz ich folgenden Code:
Delphi-Quellcode:
Nur ist das jetzt nicht sehr perfomance-schonend.
//#0 durch #32 ersetzen
for i := 1 to length(S) do begin If S[i] = #0 then S[i] := #32; end; Vor allem, weil die einzulesende Datei gute 4 MB hat und dementsprechend Zeichen. Gibt es dafür eine bessere Lösung? Noch kurz als Info: Momentan arbeite ich dann mit "S", also dem String, weiter. Ich möchte aber nun das Ganze in eine StringList laden. |
Re: #0 perfomanceschonend aus String entfernen
Delphi-Quellcode:
?
meinString := Trim(meinString);
|
Re: #0 perfomanceschonend aus String entfernen
Man könnte auch mit StringReplace() arbeiten. Das wird aber im Endeffekt auf das selbe herauskommen
|
Re: #0 perfomanceschonend aus String entfernen
Wenn man mit Zeigern arbeitet, dann sollte das Ratz-Fatz gehen (nur Assembler wäre schneller):
Delphi-Quellcode:
Wenn man "an der RTL vorbei" Strings über Zeiger verändert muss man vorher UniqueString() aufrufen.
var
p : PChar; i : integer; begin UniqueString(S); p := PChar(S); for i := 1 to length(S) do begin If p^ = #0 then p^ := #32; Inc(p); end; |
Re: #0 perfomanceschonend aus String entfernen
Warum die ganze streamerei?
Delphi-Quellcode:
Wegen 4MB macht man sich doch nicht mehr ins Hemd *G*
var
sll : tstringlist; sll.loadfromfile('MeineDatei'); sll.Text:=SringReplace(sll.Text,#0,#32); { oder eine andere Ersetzungsroutine } ... Gruß K-H |
Re: #0 perfomanceschonend aus String entfernen
TStringList hört bei #0 auf mit einlesen, also fehlt dann nach dem Einlesen so Einiges,
Aber den TStringStream hätte man sich wohl Sparen können. Und wie schon gesagt, 4 MB ist für diese FOR-Schleife doch garnichts. :gruebel: Aber wenn es unbedingt sein muß
Delphi-Quellcode:
oder gleich
var MS: TMemoryStream;
S: AnsiString; i: Integer; begin MS.LoadFromFile(Datei); For i := MS.Size - 1 downto 0 do If PAnsiChar(MS.Memory)[i] = #0 Then PAnsiChar(MS.Memory)[i] := ' '; SetLength(S, MS.Size); MoveMemory(@S[1], MS.Memory, MS.Size); StringList.Text := S;
Delphi-Quellcode:
var MS: TMemoryStream;
i: Integer; begin MS.LoadFromFile(Datei); For i := MS.Size - 1 downto 0 do If PAnsiChar(MS.Memory)[i] = #0 Then PAnsiChar(MS.Memory)[i] := ' '; StringList.LoadFormStream(MS); |
Re: #0 perfomanceschonend aus String entfernen
Hallo,
danke an alle für eure Antworten! Mir kam das Char-weise durchsteppen durch den String etwas unprofessional (billig) vor. Aber Himitsu´s MemoryStream-Lösung gefällt mir sehr gut. |
Re: #0 perfomanceschonend aus String entfernen
Zitat:
Gruß K-h |
Re: #0 perfomanceschonend aus String entfernen
Zitat:
"Professionel" ist wenn der Sourcecode einfach strukturiert und und quasi selbsterklärend ist:
Delphi-Quellcode:
und dann später:
function StrReplaceChar(const S: Ansistring; const Source, Replace: AnsiChar): string;
var I: Integer; begin Result := S; // hier besteht noch Optimierungsmöglichkeit durch Verwendung eines Zeigers for I := 1 to Length(S) do if Result[I] = Source then Result[I] := Replace; end;
Delphi-Quellcode:
Nix gegen Himitsu, aber die Lösung aus Betrag #6 mit dem Memorystream ist deutlich schwerer zu lesen.
Memo1.Lines.Text := StrReplaceChar(StringStream.DataString, #0, ' ');
Der StringStream ist dem Memorystream vorzuziehen, da es viel sicherer und einfacher ist mit Strings zu arbeiten. (du hast nur noch nicht mitgekriegt, dass man über das Property DataString direkt auf den Inhalt zugreifen kann) Die potentiell "lebensgefährliche" Funktion MoveMemory() wird vermieden und das ist sehr wichtig für die Softwarequalität. MoveMemory() ist wie eine geladene Schrotflinte unterm Bett - sie kann dir jederzeit den ganzen Fuss wegschiesen! |
Re: #0 perfomanceschonend aus String entfernen
@shmia
Zitat:
Du hast das #4 ja schon angedeutet, aber das [pre] For i:=1 to length(s) ... inc(p) [/pre] scheint mir "doppeltgemoppelt" und daher eigentlich ein Performance Killer. Wenn' unbedingt ein Pointer sein muß hätte ich auf @s[i] getippt; Gruß K-H |
Re: #0 perfomanceschonend aus String entfernen
Zitat:
Wenn da z.B. steht: s[i] , dann wird intern der Zeiger s genommen, dann wird i dazuaddiert, dies ergibt dann den Zeiger auf das zu lesende oder schreibende Zeichen. Bei einem 4MB-String wären das 8 Millionen unnötige Additionen. Hier die komplett getunte Funktion:
Delphi-Quellcode:
Hier wird der Zeiger p einmal gesetzt und dann immer nur mit Inc() weiterbewegt.
function StrReplaceChar(const S: Ansistring; const Source, Replace: AnsiChar): AnsiString;
var I: Integer; p : PAnsiChar; begin Result := S; UniqueString(Result); p := PAnsiChar(Result); for I := Length(S)-1 downto 0 do begin if p^ = Source then p^ := Replace; Inc(p); end; end; Inc(p) wird direkt in einen einzigen (schnellen) X86-Befehl übersetzt. Da die Schleifenvariable nicht mehr benützt wird, kann man die Schleife auch rückwärts laufen lassen. Schleifen, die rückwärts runter auf 0 zählen sind besonders gut in Maschinencode zu übersetzen und daher sehr schnell. |
Re: #0 perfomanceschonend aus String entfernen
[quote="shmia"]
Zitat:
Meiner Erfahrung nach ist der Unterschied in diesem Fall marginal. Und wenn ich es ausprobiere, sehe ich auch keinen Unterschied. Ich würde dann die naive For-Schleife und einen stinknormalen String mit Index verwenden:
Delphi-Quellcode:
For i:=1 to Length(s) do
if s[i]=ZuErsetzendesZeichen then s[i] := NeuesZeichen; Zitat:
Hast Du wirklich verifiziert, das deine getunte Funktion schneller ist? Bei mir ist sie das nämlich nicht. Ich stolpere immer wieder über diesen Vergleich zwischen Pointer und Indizierung. Bei solch naiven Schleifen sind beide gleich schnell, aber sobald man etwas mehr in der Schleife macht, ist die Pointer-Variante immer etwas schneller (20-30%, na ja, 'etwas'...) Um es schneller zu bekommen, würde ich mir mal ![]() |
Re: #0 perfomanceschonend aus String entfernen
Blos noch etwas Zusätzliches:
PChar(S) gibt nur einen Zeiger auf das erste String-Zeichen oder bei einem Leerstring auf einen "anderen" Bereich, welcher auf 0000 steht. @S[1] ruft UniqueString auf und gibt einen Zeiger auf das 1. Zeichen oder nil zurück. StringReplace und #0 im zu Suchstring, sowie im zu durchsuchendem String geht nicht, da CodEmba blöder Weise intern eine auf PChar-basierende Pos-Funktion nutzen, welche ja bekanntlich bei #0 stoppen. :wall: PS: diese Move-Funktionen sind insoweit nicht gefährlich, wie man auf die Datengößen achtet und weiß was man tut. Bei dem StringStream muß man in diesem Fall ab D2009 auch aufpassen, daß man nicht durch ein falsches/unpassendes Encoding die Daten zerschrottet. PSS: Aus soeinem Grund hatte ich auch extra den AnsiString, statt String verwendet. |
Re: #0 perfomanceschonend aus String entfernen
Hallo zusammen
ich hab mich dann mal aufgemacht und quickndirty versucht heraus zu bekommen was es in der Praxis bring: Zitat:
Delphi-Quellcode:
Ja ich weiß, Zeitmessung mit tickcount sind alles andere als genau, (ich hab die bessere Funktion nicht mehr gefunden) aber da der Fehler bei beiden Versionen auftritt ignorier ich ihn einfach.
function StrReplaceChar(const S: Ansistring; const Source, Replace: AnsiChar): AnsiString;
var I: Integer; p : PAnsiChar; begin Result := S; UniqueString(Result); p := PAnsiChar(Result); for I := Length(S)-1 downto 0 do begin if p^ = Source then p^ := Replace; Inc(p); end; end; function StrReplaceChar_(const S: Ansistring; const Source, Replace: AnsiChar): AnsiString; var I: Integer; begin Result := S; UniqueString(Result); for I := Length(S)-1 downto 0 do if result[i] = Source then result[i] := Replace; end; procedure TForm1.Button1Click(Sender: TObject); var i,j : integer; start1, start2, end1, end2 : integer; ts : string; begin setlength(ts,4096*1024); fillchar(ts[1],length(ts),#32); for i:=1 to 4096 do if i mod 13 =0 then ts[i]:=#0; start1:=gettickcount; for j:=0 to 255 do StrReplaceChar(ts,#0,'A'); end1:=gettickcount; {---------------------------------------------} fillchar(ts[1],length(ts),#32); for i:=1 to 4096 do if i mod 13 =0 then ts[i]:=#0; start2:=gettickcount; for j:=0 to 255 do StrReplaceChar_(ts,#0,'A'); end2:=gettickcount; memo1.lines.add('---------------------'); memo1.lines.add(inttostr(end1-start1)+' 1.lauf'); memo1.lines.add('---------------------'); memo1.lines.add(inttostr(end2-start2)+' 2.lauf'); end; Irgendwie drängt sich mir der Schluß auf, daß beide Varianten gleich schnell sind. Gruß K-H |
Re: #0 perfomanceschonend aus String entfernen
Erstmal das:
sonst würde ja schon beim ersten Durchlauf alles erstetzt und die letzten 255 Mal passiert nicht mehr viel. Und dann: Tja, da siehst du mal, wie gut der Compiler optimiert. :stupid: |
Re: #0 perfomanceschonend aus String entfernen
Zitat:
Gruß K-H |
Re: #0 perfomanceschonend aus String entfernen
:oops: Ups, ich glaub ich war vorhin so sehr in Klassenprozeduren vertieft, daß ich das Function glatt übersah. :shock:
Zitat:
Ach ja, das Andere wäre z.B. QueryPerformanceCounter gewesen, aber auf die ~16 Millisekunden kommt es bei den "langen" Messzeiten wirklich nicht an. |
Re: #0 perfomanceschonend aus String entfernen
Mann könnte annehmen, daß heute Freitag ist.
??????????????????????????????????????????????? Gruß K-H |
Alle Zeitangaben in WEZ +1. Es ist jetzt 01:01 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz