Home » Tutorials » Datenspeicherung » Streams

Streams

Daten in einer Datei speichern

Als erstes werden wir Daten mit Hilfe eines Streams in einer Datei speichern. Dafür stellt uns die VCL die Stream-Klasse TFileStream zur Verfügung.
Wir werden gleich mit etwas komplizierteren Daten arbeiten, damit das Beipsiel nicht zu trivial wird.
Wir werden mit einem Array eines Records arbeiten.

type
  TDatensatz = record
    ID: integer;
    Name: AnsiString;
  end;

  TDatenArray = array of TDatensatz;

Bevor wir Daten mit einem Stream in einer Datei speichern können, müssen wir erst einmal Daten haben.

procedure TFrmMain.BtnSaveClick(Sender: TObject);
var
  Daten: TDatenArray;
  Stream: TStream;
  I: integer;
  Len: Longint;
begin
  SetLength(Daten, 2);
  Daten[0].ID := 1;
  Daten[0].Name := 'Ein Name';
  Daten[1].ID := 2;
  Daten[1].Name := 'Ein anderer Name';

Jetzt haben wir zwei Datensätze, die wir sofort in einer Datei speichern wollen. Zum Speichern verwenden wir einen TFileStream.

Stream := TFileStream.Create('c:TempStreamTest.Datei', fmCreate);
  try

Das fmCreate sorgt dafür, dass die Datei neu angelegt wird, falls sie noch nicht existiert. Wenn es eine solche Datei bereits geben sollte, dann wird sie einfach überschrieben. Wir müssten hier eigentlich noch einigen Überprüfungscode schreiben. Es könnte z.B. sein, dass der Ordner schreibgeschützt ist oder überhaupt nicht existiert. Zwecks Übersichtlichkeit sparen wir uns das hier.
Als erstes werden wir abspeichern, wie viele Datensätze wir haben.

 Len := Length(Daten);
    Stream.Write(Len, SizeOf(Len));

Zuerst versorgen wir die Variablen Len mit der Anzahl der Datensätze. Dann schreiben wir den Wert von Len in den Stream. Zu den Umweg über die Variable Len zwingt uns die Methode Write.

function TStream.Write(const Buffer; Count: Longint): Longint; virtual;
  abstract;

Das const Buffer; ohne Typangabe bedeutet, dass einfach nur mit einem Zeiger auf den Speicher der übergebenen Variablen gearbeitet wird. Der Stream hat also keine Ahnung, um welchen Typ es sich handelt. Er muss wissen, wieviele Bytes geschrieben werden sollen. Das wird an den Parameter Count übergeben.
Wir übergeben SizeOf(Len) an Count. Da Len den Typ Longint hat, ist SizeOf(Len) = 4. Ein Longint belegt vier Bytes im Hauptspeicher. Es werden also vier Bytes in den Stream geschrieben.
Nachdem wir die Anzahl der Datensätze gespeichert haben, kommen jetzt die Datensätze selbst an die Reihe.

 for I := 0 to Length(Daten) - 1 do
    begin

Da wir keine Annahmen darüber machen wollen, wie ein dynamisches Array intern aufgebaut ist, speichern wir einen Datensatz nach dem anderen.
Wir haben in unserem Record TDatensatz ein Element vom Typ AnsiString. AnsiStrings werden von Delphi intern mit Zeigern verwaltet. Bei Bedarf, wird der Inhalt eines AnsiStrings an einen anderen Ort im Speicher kopiert. Die Daten eines Records verteilen sich also beliebig über den Hauptspeicher. Wir können demnach den Record nicht als Ganzes in dem Stream speichern, sondern müssen jedes Element des Records einzeln speichern.

Stream.Write(Daten[I].ID, SizeOf(Daten[I].ID));

Mit ID geht das genauso einfach wie vorhin mit Len. Bei AnsiStrings muss man etwas mehr Aufwand treiben.

 Len := Length(Daten[I].Name);
      Stream.Write(Len, SizeOf(Len));
      Stream.Write(PChar(Daten[I].Name)^, Len);

Zuerst müssen wir die Länge des Strings in den Stream schreiben. Das geht genauso, wie mit der Anzahl der Datensätze.
Das Schreiben des eigentlichen Strings ist dagegen etwas tricky. Wie schon gesagt, ist ein AnsiString intern eigentlich nur ein Zeiger auf den Speicherbereich, wo sich die Daten des Strings befinden. Würden wir Stream.Write(Daten[I].Name, Len); schreiben, dann würden wir nicht den String, sondern den Zeiger auf den String und alles was sich zufällig dahinter im Speicher befindet, in den Stream schreiben.
Die Typumwandlung nach PChar liefert uns den Zeiger auf den ersten Buchstaben des Strings. Da wir aber nicht den Zeiger auf den Buchstaben in dem Stream schreiben wollen, müssen wir den erst dereferenzieren. Dies wird mit dem „^“ gemacht. Jetzt sieht es für Delphi so aus, als ob wir eine Variable vom Typ Char übergeben würden. Da wie bereits gesagt, der Typ überhaupt keine Rolle spielt, sondern nur die Speicherstelle, ist es dem Stream egal, ob wir das erste Zeichen des Strings oder den ganzen String übergeben.
Damit haben wir einen Datensatz in den Stream geschrieben.

end;
  finally
    Stream.Free;
  end;
end;

Wir dürfen natürlich nicht vergessen den Speicher, den das Stream-Objekt belegt, wieder freizugeben.
Wenn Sie die Erklärungen zwischen den Codestücken heraus löschen, dann haben Sie die Methode, mit der wir Daten in eine Datei schreiben.