Entwickler-Ecke

Dateizugriff - Fehler mit TFileStream


maxk - Do 06.02.03 11:54
Titel: Fehler mit TFileStream
Ich schreibe mit folgender Procedure:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
var FS:TFileStream;
      X:string;
begin
 FS:=TFileStream.Create('C:\Test.TXT',fmCreate);
 FS.Size:=5;
 FS.Position:=0;
 X:='12345';
 FS.WriteBuffer(X,length(X));
 FS.Free;
end;


Ich bekomme keine Fehlermeldung, allerdings besteht meine Datei (C:\Text.TXT) danach nur noch aus 5 Steuerzeichen statt aus dem Text von X (12345).

Was mache ich falsch?

maxk


Udontknow - Do 06.02.03 12:00

Hi!

Strings kannst du aufgrund ihrer dynamischen Länge nicht einfach so in einen Stream speichern. Du müsstest entweder Shortstrings (z.B: String[50]) verwenden oder folgende Funktionen benutzen:


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
procedure SaveStrToStream(const Stream: TStream; const Value: String); 
//Schreibt einen dynamischen String in einen Stream 
var Len: Word; 
begin 
  Len := Length(Value); 
  Stream.WriteBuffer(Len, SizeOf(Len)); 
  if Len > 0 then 
    Stream.WriteBuffer(Pointer(Value)^, Len); 
end; 

procedure LoadStrFromStream(const Stream: TStream; var Str: String); 
//Liest einen dynamischen String aus einem Stream 
var Len: Word; 
begin 
  Stream.ReadBuffer(Len, SizeOf(Len)); 
  SetLength(Str, Len); 
  if Len > 0 then 
    Stream.ReadBuffer(Pointer(Str)^, Len); 
end;


Cu, :)
Udontknow


maximus - Do 06.02.03 12:07

hi, ich glaub dafür gibt es TStringStream 8)

mfg mx


maxk - Do 06.02.03 12:10

Geht nicht!
Aber wenn ich den ersten Aufruf von WriteBuffer weglasse funzt es.

Thx,
maxk


Udontknow - Do 06.02.03 12:30

@Maximus:

Was, wenn du noch andere Sachen im Stream hast als Strings? Z.B. Records oder Integerwerte hinter dem String? Dann scheidet TStringStream aus.

@MaxK

Du musst auch zum Einlesen dann die Prozedur LoadStrFromStream benutzen.

Also:

Quelltext
1:
2:
3:
SaveStrToStream(FS,X);

LoadStrFromStream(FS,X);


Cu, :)
Udontknow


DaRkFiRe - Fr 07.02.03 00:17


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
var FS:TFileStream; 
      X:string; 
begin 
FS:=TFileStream.Create('C:\Test.TXT',fmCreate); 
FS.Size:=5; 
FS.Position:=0; 
X:='12345'; 
FS.WriteBuffer(X,length(X)); 
FS.Free; 
end;


filestreams können (so glaube ich mal, gelesen zu haben) keinen festen größen zugewiesen werden

mit writebuffer kann man nicht auf den string selbst zeigen - das ist nur ein zeiger (wegen der delphi-internen verarbeitung von zeichenfolgen)

ich würd's so machen wollen:


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var FS:TFileStream; 
      X:string; 
begin 
FS:=TFileStream.Create('C:\Test.TXT',fmCreate); 
X:='12345'; 
FS.Write(X[1],length(X)); 
FS.Free; 
end;


Udontknow - Fr 07.02.03 09:39

@DarkFire: Ähnlich machen das ja auch oben gepostete Funktionen. Sie tun aber noch mehr.

Wie bekommst du beim Lesen heraus, wie lang der String ist, der nun eingelesen werden muss? :wink:

Schau dir noch mal die geposteten Funktionen an.

Cu, :)
Udontknow


maxk - Fr 07.02.03 10:41

Die Länge des Strings ist mir ja bekannt!

Quelltext
1:
2:
SetLength(Str,20);
FS.ReadBuffer(Str[1],20);

und andersherum:

Quelltext
1:
FS.WriteBuffer(Pointer(Str)^,length(Str));                    


Das Funktioniert (es sei denn ich habe einen Schreibfehler im Quelltext :oops: ).


Udontknow - Fr 07.02.03 13:03

Na gut, wenn du die Länge kennst, geht es. Stelle dir aber mal folgendes vor: Du musst 1000 solcher Strings speichern, 90% davon sind aber leer (''). Deine Dateigrösse würde immer konstant 20000 betragen, benutzt du aber die Funktionen, die ich gepostet habe, so ist die Datei viel kleiner, da du so nur ein Word (=2Byte) für die Länge und den vorhandenen Text speicherst.
Für leere Strings würdest du nur 2 Byte benötigen, mit deiner Methode dagegen 20 (Faktor 10!). Haben die Strings eine Länge von 20, so benötigst du mit den Routinen nur unwesentlich mehr (22 Byte).
Und wenn irgendein String doch mal länger sein sollte als 20 Zeichen, hast du Pech gehabt...

Cu, :)
Udontknow


DaRkFiRe - Fr 07.02.03 13:48

Naja - dann müsste man höchstens eine Art Load / Save Engine schreiben, die nach folgendem Prinzip verfährt:

Schreiben:
Länge des Strings (Integer @ 4 Byte / 32 Bit)
String an sich


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
procedure writeStringToStream(Stream:TStream; S:STRING);
var I:INTEGER;
begin
  I:=LENGTH(S);
  Stream.Write(I,SIZEOF(INTEGER));
  Stream.Write(S[1],I);
end;

function readStringFromStream(Stream:TStream):STRING;
var I:INTEGER;
var R:STRING;
begin
  Stream.Read(I,SIZEOF(INTEGER));
  R:=STRINGOFCHAR(#0,I);
  Stream.Read(R[1],I);
  Result:=R;
end;


Keldorn - Fr 07.02.03 13:53

habt ihr euch schonmal Twriter/Treader angeschaut?

da gibt es bereits haufenweise Procs zum lesen und speichern unterschiedlichester Variablentypen

Frank


DaRkFiRe - Fr 07.02.03 14:25

Naja - ich glaube, das waren die abstrakten Komponenten für das Schreiben in Delphi-Anwendungen - also für Komponenten selbst ( Binär-daten speichern, usw. )


maximus - Fr 07.02.03 14:37

Na klar,

da hat keldron völlig recht. Die filer klassen benutz ich auch immer wieder gerne...die dinger sind einfach genial, wenn man dynamische datei formate erstellen will.

@DaRkFiRe: ich glaub sie sind weder abstrakt, noch komponeten :wink: Man kann damit binäre, als auch text-formate speichern. Die DFM-routinen benutzen sie auch, da hast du recht.

Man kann sie prima zB in seiner eigene klasse einbauen, um solche prozeduren wie: saveToStream, loadFromStream, saveToFile, etc. zu machen.

mfg maximus