Projekt mit
1 €
unterstützen?
So geht das *:


Step 05 - Filehandling: Streams

 

Was sind Streams?

 

Streams sind übersetzt Ströme. Gemeint sind hierbei Datenströme. Diese Datenströme können Daten von verschiedenen Datentypen speichern. Es wird meist eine Einfache listenform benutzt, falls die Daten keine konstante Größe besitzen.

Zum Beispiel könnten die Daten in unserem letzten Step auch einfach in einem solchen Datenstrom gespeichert werden. Dies geschieht in dem wir zunächst die Größe der Daten, welche in diesen Datenstrom geschrieben werden sollen, in den Datenstrom schreibt, was meist ein 32-Bit-Integerwert ist und somit 4 Byte belegt, und hiernach könnten wir dann die Daten schreiben. Bei unserem Beispiel würden wir für den Vornamen einer Person zunächst die Integer-Zahl, welche die länge des Namens angibt, in den Datenstrom schreiben: "5" und dannach den Namen selbst: "Marco" .

Vorteil ist also Eindeutig die dadurch entstehende Dynamik. Zudem wird durch diese Art der Speicherung enorm an Platz gespart. Wenn man beispielsweise annimmt, dass ein Vorname maximal 15 Buchstaben hat, und man will n Vornamen speichern, dann wäre die Datei bei der in Step04 vorgestellten Taktik immer n*15 Bytes groß. Da die Länge jedoch variiert, wird die Datei wesentlich kleiner sein.

Natürlich gibt es bei kleinen Dateien kein Problem. Daher wollen wir ein kleines Tool entwickeln, welches beliebige Dateien in eine zusammenführt. Wir werden dieses Tool, genauso wie die Endung der entstehenen Dateien "paked" nennen.

 

Daten in FileStreams speichern

 

Um die hier nicht den Rahmen zu sprengen wird nur sehr wenig zur Oberfläche gesagt: Wir brauchen eigentlich lediglich zwei lade-aktions-Buttons - einer zum Packen, und einer zum auseinanderführen. Zum Laden und Speichern verwenden wir OpenDialog und SaveDialog, wobei der SaveDialog nur paked-Dateien anzeigen soll. Beim vergeben des Dateinamens soll auch überprüft werden ob die Endung .paked heist (mittels ExtractFileExt kann dies einfach verglichen werden) .

Wir werden nun beim Betätigen des Dateien zusammenführen-Buttons zwei Zenarien durcharbeiten: 1. Die Datei ist die erste Datei des paketes, also die gewählte paked-Datei existiert noch nicht, und 2. Die paked-Datei existiert und die Datei soll nun eingefügt werden.

In Delphi gesprochen sieht das ganze dann so aus:

procedure TForm1.BtnPakClick(Sender: TObject);
begin
  if OpenDialog.Execute then
  begin
    if SaveDialog.Execute then
    begin
      // Sicherstellen, dass Dateiendung stimmt:
      if ExtractFileExt(SaveDialog.FileName) <> '.paked' then
        SaveDialog.FileName := SaveDialog.FileName+'.paked';
      // Fallunterscheidung:
      if not FileExists(SaveDialog.FileName) then
      begin
        // Neue Datei
      end
      else
      begin          
        // Datei existiert bereits
      end;
    end;
  end;
end;

Nun kümmern wir uns erstmal um das Erstellen einer Datei.

Der benötigte Objekttyp ist TFileStream. Wir deklarieren also eine lokale Variable targetStream und instanziieren die Klasse. In der Create-Methode haben wir einen Parameter "Mode". Hier eine kleine Liste mit den möglichen Werten:

  • fmCreate - erstellt die Datei und öffnet den Stream zum Schreiben. Vorhandene Daten werden geleert.
  • fmOpenRead - öffnet lediglich den Lesemodus. Hierbei kann die Datei während dessen auch von anderen Programmen lesend benutzt werden.
  • fmOpenWrite - öffnet lediglich den Schreibmodus. Die Datei ist für andere Programme gesperrt. Es ist nur Schreiben möglich.
Weitere Modi können unter der Delphi Hilfe nachgelesen werden:
ms-help://borland.bds4/bds4win32api_win32/html/DelphiWin32_ClassesTFileStreamCreateMethod.htm

Da wir die Information, wie die Datei einst hies, nicht verlieren wollen, werden wir zunächst den Dateinamen in den Stream schreiben. Den Dateinamen bekommen wir durch die Funktion ExtractFileName(OpenDialog.FileName). OpenDialog.Filename beinhaltet auch den Pfad zur Datei, welchen wir nicht speichern möchten.

Bisher sieht es also so aus:

          targetStream := TFileStream.Create( SaveDialog.FileName, fmCreate);
          fName := ExtractFileName( OpenDialog.FileName);
          fNameLength := Length( FName);
          targetStream.Write( fNameLength, SizeOf(fNameLength));
          // Char für Char eintragen, ganze Strings gehen nicht. Wieso?
          for i := 1 to fNameLength do
            targetStream.Write( fName[i], SizeOf(fName[i]));
Nun stellt sich die Frage: Wie schaffen wir es, eine andere, uns unbekannte Datei, hier reinzupacken? Und die Antwort ist einfach: Wir öffnen einen weiteren (ReadOnly-)Stream und kopieren mittels CopyFrom dessen Inhalt in unser targetStream rein ;)

          sourceStream := TFileStream.Create( OpenDialog.FileName, fmOpenRead);
          fileSize := sourceStream.Size;
          targetStream.Write(fileSize, SizeOf(fileSize));
          targetStream.CopyFrom(sourceStream, sourceStream.Size);
          sourceStream.Free;
Und schon funktioniert das Erstellen der paked-Datei.

Also kümmern wir uns um das weitere Hinzufügen von Dateien.

Wir wissen, vom vorherigen Kapitel, dass man eine geöffnete Datei immer zuerst resetten muss, damit man am Anfang der Datei ist. Wir brauchen jedoch das Ende des Streams, wo wir dann weitere Daten anhängen können. Also versuchen wir es einfach mal, dass wir den oberen Quellcode übernehmen, mit dem Unterschied, dass wir anstatt fmCreate, fmOpenWrite benutzen. Wenn wir es jedoch bei dieser Änderung belassen, werden wir kein Erfolg haben, da beim Öffnen des Streams sofort auf den Anfang gezeigt wird. Wir setzen also per Seek-Methode den Zeiger auf das Ende des Streams.

          targetStream := TFileStream.Create( SaveDialog.FileName, fmOpenWrite);
          targetStream.Seek( 0, soEnd);
Hier sehen wir, dass die Methode Seek wunderlicherweise zwei Parameter hat. Einmal ein "Offset" und dazu noch ein "Origin". Das ist logisch, denn das Offset gibt an wie weit der Lesezeiger verschoben werden soll. Origin bedeutet Ursprung und kann somit am Anfang des Streems sein (soBeginning), an der momentanen Position (soCurrent), oder wie wir es brauchen, am Ende des Streams mit soEnd.

Und hiermit ist das komplette Erstellen einer paked-Datei abgeschlossen. Also müssen wir nun nur noch:

 

Daten aus FileStreams laden

 

Damit wir nun kein weiteren OpenDialog brauchen, werden wir den vorhandenen während der Laufzeit modifizieren. Wir müssen lediglich den Filter neu setzen. Beim Öffnen einer beliebigen Datei ist er also 'Alle Dateien|*.*' und hier brauchen wir nur die '*.baked'-Dateien.

Nach dieser Modifikation können wir - kurzgefasst - wie folgt fortfahren:
Wir öffnen den paked-Stream lesend.
Solange wir nicht am Ende des Streams sind,
    lesen wir den Dateinamen fn aus,
    öffnen mit fmCreate einen weiteren Stream mit dem Dateinamen fn,
    kopieren die Daten der Datei in den neuen Dateistream,
    schließen ('befreien') den neuen Stream.
Wenn dies fertig ist, schließen wir den paked-Stream.

Wenn ihr beim Lesen der Daten aufgepasst habt und euch auch mal die Methoden, welche mit TFileStream mitgeliefert werden, angeschaut habt, dann sollte dies hier ein Kinderspiel werden.

Falls doch etwas nicht ganz geklappt hat, dann schaut euch entweder den Source an, oder schreibt etwas unten in die Kommentarbox.

Nun können noch viele Verbesserungen durchgeführt werden. Zum einen könnte es ja mal sein, dass zwei Dateien den gleichen Namen besitzen. Oder man möchte nicht in dem Ordner 'entpacken' in dem die paked-Datei liegt, sondern irgendwo anderst. Oder man möchte einen kompletten Ordner mit allen Unterordnern in die paked-Datei stecken. Oder oder oder... ;-)

Also, ich hoffe ihr habt hierbei viel gelernt ;-)


Da das Kommentarmodul dieser Seite zur Zeit neu überarbeitet werden muss, sendet bitte alle Fragen, Anregungen oder Probleme mit Betreff zu welchem Thema es sich handelt an folgende Mailadresse:



www.marco-hetzel.de
www.delphi-lernen.de
www.lazarus-lernen.de

© 2006-2019 by Marco Hetzel , last modified 01.11.2018, 11:28

(* Unterstützung dient der Aufrechthaltung laufender Kosten für dieses Projekt.)