29. oktober 2001 - 22:02Der er
15 kommentarer og 1 løsning
Skrive en tekststeng i en TMemoryStream
jeg har lidt problemer med at bruge en TmemoryStream, elelr i hvert fald på den mest optimale måde. Jeg har lavet et lille eksempel på hvad jeg mener. I det har jeg en liste over personer. Problemet opstår når jeg skal kalde TMemoryStreams Write og så skrive en tekststreng. Først gjorde jeg det som en udkommenterede kode, der skriver en Char af gangen. Men så fandt jeg frem til en anden måde at skrive en streng på en gang.
type PPerson = ^TPerson; TPerson = record Nummer: Integer; Navn: string; end;
var Liste: TList;
-----------------------
procedure Hejsa; Var EnPerson: PPerson; I, J, Str: Integer; MyStream: TMemoryStream; tmpChar: Char; begin if Liste = nil then exit else if Liste.Count = 0 then begin ListBox1.Items.Clear; exit; end;
MyStream := TMemoryStream.Create; For i := Liste.IndexOf(Liste.First) to Liste.IndexOf(Liste.Last) do begin EnPerson := Liste.Items[i];
Str := Length(EnPerson^.Navn); MyStream.WriteBuffer(Str, SizeOf(Integer)); // for j := 1 to Str do // begin // tmpChar := EnPerson^.Navn[j]; // MyStream.WriteBuffer(tmpChar, 1); // end; MyStream.WriteBuffer(Pointer(EnPerson^.Navn)^, Str); MyStream.WriteBuffer(EnPerson^.Nummer, SizeOf(EnPerson^.Nummer)); end; end;
Det jeg så gerne vil have at vide er: - Hvordan virker det jeg har skrevet med pointer uden om det hele egentlig? - Når jeg skal hente, kan jeg så gøre det på en ligende måde, eller skal jeg løbe hvert bogstav igennem?
Såvidt jeg kan se, så ønsker du at skrive alle dine TPerson i en stream, og så have mulighed for at læse dem igen... her er et eksempel, hvor der r lagt stor vægt på performance:
function TForm1.SkrivPerson(Person: PPerson; Stream: TMemoryStream): boolean; var Len: byte; { går ud fra at navn ikke er længere end 255 } begin try Len := Length(Person.Navn); Stream.WriteBuffer(Person.Nummer, SizeOf(Person.Nummer)); Stream.WriteBuffer(Len, SizeOf(Len)); Stream.WriteBuffer(pchar(Person.Navn)^, Len); Result := TRUE; except Result := FALSE; end; end;
function TForm1.SkrivAlle(Liste: TList; Stream: TMemoryStream): boolean; var i : integer; Count : integer; begin try Count := Liste.Count; Stream.WriteBuffer(Count, SizeOf(Count));
for i:=0 to Liste.Count-1 do SkrivPerson(Liste[i], Stream);
Result := TRUE; except Result := FALSE; end; end;
function TForm1.HentAlle(Liste: TList; Stream: TMemoryStream): boolean; var i : integer; Count : integer; Person: PPerson; begin try Stream.ReadBuffer(Count, SizeOf(Count)); Liste.Capacity := Count; for i:=0 to Count-1 do begin New(Person); HentPerson(Person, Stream); Liste.Add(Person); end; Result := TRUE; except Result := FALSE; end; end;
function TForm1.HentPerson(Person: PPerson; Stream: TMemoryStream): boolean; var Len: byte; { går ud fra at navn ikke er længere end 255 } begin try Stream.ReadBuffer(Person.Nummer, SizeOf(Person.Nummer)); Stream.ReadBuffer(Len, SizeOf(Len)); SetLength(Person.Navn, Len); Stream.ReadBuffer(pchar(Person.Navn)^, Len); Result := TRUE; except Result := FALSE; end; end;
procedure TForm1.Button1Click(Sender: TObject); var Person : PPerson; Stream : TMemoryStream; L : TList; i : integer; begin Stream := TMemoryStream.Create; L := TList.Create;
for i:=0 to 9 do begin New(Person); Person.Nummer := i; Person.Navn := \'Navn #\'+inttostr(i); L.Add(Person); end;
SkrivAlle(L, Stream);
for i:=0 to L.Count-1 do Dispose(PPerson(L[i])); L.Clear;
Stream.Position := 0;
HentAlle(L, Stream);
for i:=0 to L.Count-1 do begin Memo1.Lines.Add(PPerson(L[i])^.Navn); end;
L.Free; Stream.Free; end;
Button1Click proceduren er der for at se om det virker, og det ser ud til at fungere ok.
Jeg glemte lige det absolut vigtigste, hvis det skal gå stærkt:
function TForm1.ListeStoerrelse(Liste: TList): integer; var i : integer; begin try Result := 0; for i:=0 to Liste.Count-1 do begin Inc(Result, SizeOf(PPerson(Liste[i])^.Nummer)); Inc(Result, Length(PPerson(Liste[i])^.Navn)+SizeOf(byte)); // byte er længden på string (max. 255) end; except Result := -1; end; end;
Så meget vil dine data fylde i stream\'en, og det bruger vi på følgende måde:
function TForm1.SkrivAlle(Liste: TList; Stream: TMemoryStream): boolean; var i : integer; Count : integer; begin try Count := Liste.Count; --> Stream.SetSize(ListeStoerrelse(Liste) + SizeOf(Count)); Stream.WriteBuffer(Count, SizeOf(Count));
for i:=0 to Liste.Count-1 do SkrivPerson(Liste[i], Stream);
Result := TRUE; except Result := FALSE; end; end;
Hvis du tilføjer den, vil det gå ca. 10x så hurtigt at skrive dine records i stream\'en (jeg har lige lavet en tidstest). Alle streams er VANVITTIG ineffektive hvis der skrives mange gange i dem. Der burde være en advarsel i Delphi hjælpen...
>- Hvordan virker det jeg har skrevet med pointer uden om det hele egentlig?
En Delphi string er en pointer (hvis du tager SizeOf på den giver det 4). Når du så skriver pointer(str)^, så får du adressen hvor indholdet af str ligger. Lige før indholdet af str ligger længden, samt en reference count som bruges internt i Delhpi, men dem holder compileren skjult for dig. Compileren sørger også for at en string altid er nul termineret, så du kan typecaste til pchar - og bruge den som en C string - uden problemer.
martinlind >> jeg tror at dit kan bruges men det forklare ikke så meget af det jeg søgte, og jeg tror også dit er lidt langsomt.
unclebug >> Jeg har lige et par spørgsmål til din kode: - Da du i dit eksempel vil frigøre det hele bruger du både TList.Clear og du siger også Dispose(). Hvorfor begge?
- Du siger at Streams er langsomme hvis der skrives mange gange. Men det gør du da også i det sidste eksempel. Dog sætter du størrelsen.
martinlind >> jeg tror at dit kan bruges men det forklare ikke så meget af det jeg søgte, og jeg tror også dit er lidt langsomt
Martindlinds\' gør det samme som du selv er kommet frem til, bare langsommere. Og den viser heller ikke læsning, kun skrivning. Læsningen kan ikke laves på samme måde, der er man nødt til at angive længden på den string der skal læses. Derfor gemmer jeg string længderne i eksemplet, så jeg ikke skal scanne efter nul terminering.
Jeg bruger Dispose fordi TList ikke går ind og Free\'er de items den peger på, det skal man selv gøre. De vil godt nok blive frigivet når du lukker dit Delphi program, men ikke før.
Hver eneste gang der skrives til en stream, checker den om der er plads til det man vil skrive. Hvis der ikke er, så udviddes den med den count man angiver i WriteBuffer. Det er en meget langsom måde at gøre det på, da der skal allokeres memory hele tiden. Istedet sætter jeg size, så skal der ikke allokeres ny memory en eneste gang mens streamen fyldes op. Jeg prøvede at skrive 100000 af dine records uden at sætte size, det tog ca. 1.5 sekunder. Så kørte jeg den samme hvor size var sat på forhånd, og det tog ca. 150 ms (0.15 sek.). Jeg kørte den flere gange, med samme resultat, altså ~10x gange så hurtigt hvis size sættes først.
unclebug >> Jeg kan godt forstå din måde at gøre det på, men den jeg skrev var en jeg fandt i deplhi\'s egen måde at gemme lange strenge under TWriter og så troede jeg at det var den bedste måde.
I virkeligheden har jeg planer om at bruge classes så jeg ved ikke om du har en smartere måde der, for det der tager lang tid der er når den skal create for hvert objekt der tilføjes. I mit tilfælde bruger jeg jo bare new
Jeg gik lige ind og kiggede på TWriter, og den gør det på samme måde som mig. Skriver først 1 byte der indeholder længden på string\'en, og derefter data.
Men jeg er jo så heldig at jeg ved præcis hvor mange data der skal i streamen (det kan jeg taelle i ListeStoerrelse), så jeg sætter size med det samme. Og det er det der virkelig tæller. Det er ikke så vigtigt i TWriter, for den skal næppe skrive 100000 strings. Det ved jeg jo heller ikke om du skal, men nu kan du gøre det lynhurtigt.
>I virkeligheden har jeg planer om at bruge classes Mener du istedet for TPerson records?
og årsagen til din tid\'s besparelse er at du kan alloc\'re hele mem. bloken på en gang og ikke behøver lave realloc hver gang, en realloc tager tid, da data skal bevares
unclebug >> ja nemlig, men det jeg har lavet med classes har været betydelig langsomere end dette så jeg tror næsten bare at jeg bruger din kode, for den er virkelig hurtig.
Det der med at skrive længden af tekststregen gør jeg jo også i mit første eksempel. Det jeg henviste til ved TWriter, var at den sagde det der pointer(tekst)^
martinlind >> det var også det jeg gik ud fra der skete og blev forbedret, men jeg havdew ikke lige eslv tænkt på at det kunne hjælpe.
>Det der med at skrive længden af tekststregen gør jeg jo også i mit første eksempel. Det jeg henviste til ved TWriter, var at den sagde det der pointer(tekst)^
Ups, det gør du jo også! Jeg scrollede endda op for at se om du gjorde, men jeg missede det. Jo, det er en hurtigste måde at skrive (og især læse) en string. Men det er Size der er det vigtigste.
Classes vil bare gøre det langsommere (ikke meget dog), så med mindre der er en god grund til det, så lad være.
Hvordan ville du så læse ved at bruge pointer(str)^ for det kan jeg ikke finde ud af. Er det lige meget om man laver det om til en PChar som dig eller den måde jeg forsøgte når man tænker på hastighed?
Om du bruger pointer eller pchar er helt ligegyldigt i dette tilfælde, de er begge pointere, og giver den samme performance. Du kan ændre min pchar til pointer, samme difference...
Først læses længden, derefter sættes string længden med SetLength til den længde er lige er læst (den er vigtig), og så læses string\'en ind... Næsten det samme som skrivning.
Synes godt om
Ny brugerNybegynder
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.