Entwickler-Ecke

Dateizugriff - string in memorystream hinzufügen...[gelöst]


patrick - Sa 05.07.03 12:26
Titel: string in memorystream hinzufügen...[gelöst]
hallo zusammen,
ich arbeite gerade zum ersten mal richig mit memorystreams und
meine bücher können mir nicht so richtig weiterhelfen. :cry:

ich habe in meinem programm eine funktion welche in eine repeat-schleife kleine wörter(strings) mit einer länge zwischen 1 und 5 zeichen erstellt.
diese erstellten wörter sollen anschleißend zu den anderen (davor erstellten wörtern) in dem memorystream hinzugefügt werden.

wenn die schleife beendet wurde, soll der gesamte inhalt des streams in die variable result ausgelesen werden, welche anschließend in ein memo-feld kopiert wird.

leider funktioniert der unten stehende quelltext nicht.
(es sei den sinn des codes wäre gewesen eine access violation hervorzurufen!)
wo liegt mein (denk)-fehler?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
repeat
{...
erstellung der wörter 
das resultat wird in der variable "tmp" gespeichert
...}

mstream.Write(tmp,(sizeof(tmp)*length(tmp));
inc(current);
until
(max=current) or
(GetAsyncKeystate(Vk_Escape) <> 0);

mstream.Position:=0;
mstream.Read(result,mstream.Size);
mstream.Clear;
mstream.Free;


statt mit

Delphi-Quelltext
1:
mstream.Read(result,mstream.Size-sizeof(string));                    

hab ich es auch schon mit

Delphi-Quelltext
1:
memo1.lines.readfromstream(mstream)                    

probiert aber als ergebnis kommen nur bruchstücke des ursprünglichen codes herraus (bei 1000 druchläufen nur 2 zeichen!)
obwohl mstream.Size die richtige größe hatte.

ich hoffe mir kann jemand helfen
THX


Andreas Pfau - Sa 05.07.03 20:38

Hallo,

so wie ich das verstehe willst du einen String (Deklariert als "STRING") in einen Stream schreiben. Nöö, geht net. Warum? Ein string enthält keine Daten. Das ist eigentlich nur ein Zeiger, also 32Bit, die auf den Speicherbereich verweisen, wo der eigentliche Text ist. Du musst also die Daten UND deren Länge hinterlegen. So:

Delphi-Quelltext
1:
2:
3:
Len := Length(Str);
Stream.Write(Len, SizeOf(Len));
StreamWrite(Pointer(Str)^, Len);

Warum ich den String so komisch in Pointer umwandle? Anders macht dir der Compiler Stress! OK, und so liest man:

Delphi-Quelltext
1:
2:
3:
Stream.Read(Len, SizeOf(Len));
SetLength(Str, Len);
Stream.Read(Pointer(Str)^, Len);

Ready. War es das, was du wolltest?

Moderiert von user profile iconTino: Code- durch Delphi-Tags ersetzt.


patrick - So 06.07.03 12:28

ich werds (vielleicht) heute mal versuchen wenn ich dazu komme...


patrick - Do 10.07.03 18:48

ist es möglich den gesamten vorher häppchenweise eingelesenen string auf einmal auszulesen?
beipiel:
ich schreibe in den stream drei mal daten:
teil_eins_
teil_zwei_
teil_drei_

und möchte den gesamten stream nun auf einen schlag auslesen so das dies anschließen so aussieht:
teil_eins_teil_zwei_teil_drei

geht das?


Andreas Pfau - Do 10.07.03 19:14

Hallo,

entweder du liest die 3 Teile ainzeln aus und setzt sie zusammen, oder, was ich tun würde, sie vorher in einen einzelnen Stream zusammensetzen, dann als einzelnen String streamen.[/code]


patrick - Fr 11.07.03 10:49

genau das wollte ich ja vermeiden:
das programm macht ca 10000-20000 durchläufe wo es einen kleinen string an die anderen, davor erstellten strings, anknüpft.
dies hab ich zuerst so geregelt:


Delphi-Quelltext
1:
gesamt_string:=gesamt_string+teilstring;                    

nachteil: je länger die durchläufe dauern, desto langsamer wird das programm, da der string immer länger wird.

deshalb wollte ich die ganzen einzelstrings in den stream laden und nach beendigung der schleife den gesamten stream auf einmal auslesen.


Andreas Pfau - Fr 11.07.03 13:21

Hallo,

dann mach es so:
- schreib zuerst die Länge ALLER Strings rein (Length(S1) + Length...)
- Dann String1, String2, String3 einzeln schreiben, ohne weitere Längenangabe

Dann kannst du es auf einen Rutsch auslesen, als ob#s nur einer Wäre.


patrick - Mo 14.07.03 12:32

ich werd es irgendwann diese woche versuche, hab viel um die ohren
*sorry*


patrick - Mo 14.07.03 17:51

Andreas Pfau hat folgendes geschrieben:
Hallo,

dann mach es so:
- schreib zuerst die Länge ALLER Strings rein (Length(S1) + Length...)
- Dann String1, String2, String3 einzeln schreiben, ohne weitere Längenangabe

Dann kannst du es auf einen Rutsch auslesen, als ob#s nur einer Wäre.


ich kriegs nicht hin :cry: , kannst du mir den quelltext mal vorgeben?
ne kleine erklärung zum quelltext wäre auch nicht schlecht damit ich nicht ewig dumm bleibe :(


Andreas Pfau - Mo 14.07.03 17:57

Hallo,

hier ein kleiner Code:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
procedure TMain.Button1Click(Sender: TObject);
var
  F: TFileStream;
  S1, S2, S3: string;
  Len: Integer;
begin
  S1 := 'Ein';
  S2 := ' kleines';
  S3 := ' Beispiel.';
  Len := Length(S1) + Length(S2) + Length(S3);

  F := TFileStream.Create('Test.dat', fmCreate);
  F.Write(Len, SizeOf(Len));
  F.Write(Pointer(S1)^, Length(S1));
  F.Write(Pointer(S2)^, Length(S2));
  F.Write(Pointer(S3)^, Length(S3));
  F.Free;
end;

procedure TMain.Button2Click(Sender: TObject);
var
  F: TFileStream;
  S: string;
  Len: Integer;
begin
  F := TFileStream.Create('Test.dat', fmOpenRead);
  F.Read(Len, SizeOf(Len));
  SetLength(S, Len);
  F.Read(Pointer(S)^, Length(S));
  F.Free;

  ShowMessage(S);
end;


Button1.Click -> Schreiben
Button2.Click -> Lesen

Noch Fragen :?:


Andreas Pfau - Mo 14.07.03 18:56

Hallo nochmal,

sorry, Comments vergessen :oops:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
procedure TMain.Button1Click(Sender: TObject);
var
  F: TFileStream;
  S1, S2, S3: string;
  Len: Integer;
begin
  // Strings setzen
  S1 := 'Ein';
  S2 := ' kleines';
  S3 := ' Beispiel.';

  // Länge der Strigns auslesen und addieren
  Len := Length(S1) + Length(S2) + Length(S3);

  // Stream erzeugen
  F := TFileStream.Create('Test.dat', fmCreate);

  // Gesamtlänge schreiben
  F.Write(Len, SizeOf(Len));

  // Einzelne Strings schreiben
  F.Write(Pointer(S1)^, Length(S1));
  F.Write(Pointer(S2)^, Length(S2));
  F.Write(Pointer(S3)^, Length(S3));

  // Stream freigeben
  F.Free;
end;

procedure TMain.Button2Click(Sender: TObject);
var
  F: TFileStream;
  S: string;
  Len: Integer;
begin
  // Stream erzeugen
  F := TFileStream.Create('Test.dat', fmOpenRead);

  // Länge auslesen
  F.Read(Len, SizeOf(Len));

  // Länge des Strings setzen
  SetLength(S, Len);

  // String auslesen
  F.Read(Pointer(S)^, Length(S));

  // Stream freigeben
  F.Free;

  // Gelesenen Stream anzeigen
  ShowMessage(S);
end;


Ich denke, jetzt müsste alles ersichtlich sein.


AndyB - Mo 14.07.03 20:05

patrick hat folgendes geschrieben:
das programm macht ca 10000-20000 durchläufe wo es einen kleinen string an die anderen, davor erstellten strings, anknüpft. [...]

Delphi-Quelltext
1:
gesamt_string:=gesamt_string+teilstring;                    

Du misshandelst damit den Speichermanager und der revanchiert sich mit einem extremen Speicherverbauch. Das kann sogar mehr als 200 MB Ram verschlingen obwohl du nur 1 MB Daten hast.

Zitat:
nachteil: je länger die durchläufe dauern, desto langsamer wird das programm, da der string immer länger wird.

Das das Syntom der Misshandlung. Windows muss jedes zusätzliche Byte in das PageFile schreiben und auch wieder auslesen. Und da die Festplatte im Vergleich zum RAM sehr sehr langsam ist, wird dein Programm eben auch langsamer.


Zitat:
deshalb wollte ich die ganzen einzelstrings in den stream laden und nach beendigung der schleife den gesamten stream auf einmal auslesen.

Damit schiebst du die Misshandlung von den Stringfunktionen auf die Streamfunktionen. Im Endeffekt nimmt dadurch der Speicherverbrauch sogar noch stärker zu.



Woher bekommst du die Teilstrings? Aus einer StringList, aus einer Datei, von einer Funktion?


patrick - Di 15.07.03 12:03

@Andreas Pfau THX :D


@ AndyB von einer funktion


AndyB - Di 15.07.03 13:48

Braucht diese Funktion lange, bis sie den String zurückliefert? Wenn nicht, dann solltest du zuerst die Länge aller Strings zusammen ermitteln und dann mit SetLength() den Zielstring auf die Gesamtlänge setzen. Danach kannst du mit Move() und einem PChar die Zeichen kopieren.
Bei wenigen Strings ist dies zwar erheblich langsamer, aber wenn du dann auf über 1000 Stringzusammensetzungen kommst, dreht sich der Geschwindigkeitsgewinn um.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
MaxLen := 0;
for i := 0 to Anzahl - 1 do
  MaxLen := MaxLen + Length(Funktion(i));

SetLength(S, MaxLen);

if MaxLen > 0 then
begin
  P := PChar(S);
  for i := 0 to Anzahl - 1 do
  begin
    Value := Funktion(i);
    if Value <> '' then
    begin
      Move(Value[1], P^, Length(Value));
      Inc(P, Length(Value));
    end;
  end;
end;

// hier steht S zur Verfügung.


AndyB - Di 15.07.03 13:55

Braucht die Funktion hingegen lange, dann musst du einen Pauschalwert für die Länge des Strings benutzen, den du dann immer um ein gewisses Maß verlängerst.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
RealLen := 0;
Len := 1024 * 1024;
SetLength(S, Len); // 1 MB

for i := 0 to Anzahl - 1 do
begin
  Value := Funktion(i);

  ValueLen := Length(Value);
  if RealLen + ValueLen > Len then
  begin
    Inc(Len, Max(1024 * 1024, ValueLen)); // mindestens 1 weiteres MB reservieren
    SetLength(S, Len);
  end;
  if ValueLen > 0 then
  begin
    Move(S[RealLen + 1], Value[1], ValueLen);
    Inc(RealLen, ValueLen);
  end;
end;
SetLength(S, RealLen); // S auf die wahre Länge kürzen


patrick - Mi 16.07.03 12:49

danke ich werde beides ausprobieren... :D