Autor |
Beitrag |
catweasel
Beiträge: 487
Erhaltene Danke: 1
Win 7 64bit
Delphi 7 Second Sedition V7.2
|
Verfasst: Do 10.01.13 21:25
Hi,
Ich habe folgendes Problem:
Ich möchte verschieden Datentypen in einer Datei speichern.
Bevor jetzt aber Vorshläge zum Thema "Records" kommen: Das nützt mir nichts.
Nehmen wir mal folgende Typen:
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:
| type TData=record Foo : integer; Bar : integer; end;
type TDataMap = array of array of TData;
type TDataFile = file of TData;
type TDataClas=class(TObject) private FData : TDataMap; FMapSize : TPoint; FMapName : string; public procedure LoadFromFile(filename:string); procedure SaveToFile(filename:string); end; |
Ich könnte nun ein TDataFile abspeihern und tricksen indem ich die Grösse des Arrays in einem "Header record" packe und zuerst abspeichere. Die Datei hätte dann einen Record mehr als DataMap Elemente hat, aber das würde funktionieren. Aber wie kann ich nun den Daten und den Namen in einer Datei abspeichern?
In diesem Fall kann ich kein Datenfeld als Header missbrauchen (Foo und Bar sind beides Integer). Nur wegen dem Namen ein Datenfeld hinzuzufügen ist "schlecht" weil ich den Namen ja nur einmal pro Datei habe und nicht für jedes Datenfeld. Ich kann auch keinen Record definieren der Daten und den Namen enthält und dann einen Record abspeichern, da TDataMap keine feste Länge hat und daher tabu ist.
Wie speichert man gemischte Daten
Wäre Dankbar für etwas Aufklärung
Cheers,
Catweasel
_________________ Pommes werden schneller fertig wenn man sie vor dem Frittieren einige Minuten in siedendes Fett legt.
|
|
rushifell
Beiträge: 306
Erhaltene Danke: 14
|
Verfasst: Do 10.01.13 21:45
So ganz hab ich Dein Problem nicht verstanden. Hier eine Möglichkeit:
Quelltext 1: 2: 3: 4: 5:
| Datentyp (Konstante Länge, z.B. 1 Byte) Länge Beschreibung (Konstante Länge, z.B. 2 Bytes) Beschreibung (variable Länge) Länge der Daten (Konstante Länge, z.B. 4 Bytes) Daten (variable Länge) |
Je nach Datentyp kannst Du den Aufbau der Folgedaten anders gestalten. Prüfsummen zum Schützen vor Manipulation sind auch denkbar.
Gruß
|
|
catweasel
Beiträge: 487
Erhaltene Danke: 1
Win 7 64bit
Delphi 7 Second Sedition V7.2
|
Verfasst: Do 10.01.13 22:31
rushifell hat folgendes geschrieben : | So ganz hab ich Dein Problem nicht verstanden. Hier eine Möglichkeit:
Quelltext 1: 2: 3: 4: 5:
| Datentyp (Konstante Länge, z.B. 1 Byte) Länge Beschreibung (Konstante Länge, z.B. 2 Bytes) Beschreibung (variable Länge) Länge der Daten (Konstante Länge, z.B. 4 Bytes) Daten (variable Länge) |
Je nach Datentyp kannst Du den Aufbau der Folgedaten anders gestalten. Prüfsummen zum Schützen vor Manipulation sind auch denkbar.
Gruß |
Hi,
Hmm, ich hab deine Antwort nicht verstanden.
Wie würde das denn praktisch aussehen wenn ich einen String einen Integer und ein dynamisches array von records speichern will?
Sähe die Datei dann so aus?
(Je ein Byte pro Zeile)
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| 42 6 D e l p h i |
Was meinst du mit "Beschreibung"?
Das läuft also darauf hinaus eine Datei byteweise zusammen und auseinander zu pfriemeln und Datentyoen wie integer, extended und andere mehrbyte Typen aus einzelnen gelesenen Bytes schneidern?
Oder hab ich da was falsch verstanden?
Cheers,
Catweasel
_________________ Pommes werden schneller fertig wenn man sie vor dem Frittieren einige Minuten in siedendes Fett legt.
|
|
rushifell
Beiträge: 306
Erhaltene Danke: 14
|
Verfasst: Do 10.01.13 22:46
catweasel hat folgendes geschrieben: | Sähe die Datei dann so aus? |
Im Grunde ja. Mit Datentyp meinte ich jedoch den Aufbau, der Folgedaten. Sorry, hab mich da etwas ungünstig ausgedrückt.
catweasel hat folgendes geschrieben: |
Wie würde das denn praktisch aussehen wenn ich einen String einen Integer und ein dynamisches array von records speichern will? |
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
| Var w:Word; S:String; i:Integer; LengthDynArray:Cardinal; ... Stream.Read(w,Sizeof(Word); Stream.Read(S,w); ShowMessage(S); Stream.Read(i,4); ShowMessage(IntToStr(i)); Stream.Read(LengthDynArray,4); Stream.Read(DynArray,LengthDynArray); |
Nur vom Prinzip her.
|
|
jaenicke
Beiträge: 19284
Erhaltene Danke: 1742
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Do 10.01.13 22:48
Wobei ich einfach eine Klasse nehmen würde, dieser die Methoden LoadFromStream und SaveToStream, dazu noch eine Klasse für die gesamte Datei, ebenfalls mit diesen Methoden und dazu LoadFromFile und SaveToFile. Dann ist das alles wunderbar gekapselt. Ein Beispiel müsste ich mal schreiben...
|
|
Lemmy
Beiträge: 792
Erhaltene Danke: 49
Windows 7 / 10; CentOS 7; LinuxMint
Delphi 7-XE10.1, VS 2015
|
Verfasst: Do 10.01.13 23:01
Hallo,
was spricht eigentlich gegen eine XML Datei?
Grüße
|
|
catweasel
Beiträge: 487
Erhaltene Danke: 1
Win 7 64bit
Delphi 7 Second Sedition V7.2
|
Verfasst: Do 10.01.13 23:09
jaenicke hat folgendes geschrieben : | Wobei ich einfach eine Klasse nehmen würde, dieser die Methoden LoadFromStream und SaveToStream, dazu noch eine Klasse für die gesamte Datei, ebenfalls mit diesen Methoden und dazu LoadFromFile und SaveToFile. Dann ist das alles wunderbar gekapselt. Ein Beispiel müsste ich mal schreiben... |
Hab ich doch vor.
Guck mal die TDataClass Definition am Threadanfang an
Cheers,
Catweasel
_________________ Pommes werden schneller fertig wenn man sie vor dem Frittieren einige Minuten in siedendes Fett legt.
|
|
catweasel
Beiträge: 487
Erhaltene Danke: 1
Win 7 64bit
Delphi 7 Second Sedition V7.2
|
Verfasst: Do 10.01.13 23:17
rushifell hat folgendes geschrieben : | catweasel hat folgendes geschrieben: | Sähe die Datei dann so aus? |
Im Grunde ja. Mit Datentyp meinte ich jedoch den Aufbau, der Folgedaten. Sorry, hab mich da etwas ungünstig ausgedrückt.
catweasel hat folgendes geschrieben: |
Wie würde das denn praktisch aussehen wenn ich einen String einen Integer und ein dynamisches array von records speichern will? |
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
| Var w:Word; S:String; i:Integer; LengthDynArray:Cardinal; ... Stream.Read(w,Sizeof(Word); Stream.Read(S,w); ShowMessage(S); Stream.Read(i,4); ShowMessage(IntToStr(i)); Stream.Read(LengthDynArray,4); Stream.Read(DynArray,LengthDynArray); |
Nur vom Prinzip her. |
Ah ok. Mit FileStreams hatte ich bisher kaum gearbeitet. Immer nur mit typisierten Dateien (im rudimentärsten Fall vom Typ Byte). Eine Frage habe ich aber noch zu:
Delphi-Quelltext 1: 2:
| Stream.Read(LengthDynArray,4); Stream.Read(DynArray,LengthDynArray); |
Erst wird die Länge als 4 Byte Cardinal eingelesen. Das check ich noch
Aber muss ich da nicht dann noch ein Setlength(DynArray,LengthDynArray); einfügen bevor ich die Daten lese?
Ich werde mich auf jeden Fall mal mit Streams auseinandersetzen.
Danke für den Tip
Cheers,
Catweasel
_________________ Pommes werden schneller fertig wenn man sie vor dem Frittieren einige Minuten in siedendes Fett legt.
|
|
Lannes
Beiträge: 2352
Erhaltene Danke: 4
Win XP, 95, 3.11, IE6
D3 Prof, D4 Standard, D2005 PE, TurboDelphi, Lazarus, D2010
|
Verfasst: Fr 11.01.13 01:07
Hallo,
catweasel hat folgendes geschrieben : |
Ich werde mich auf jeden Fall mal mit Streams auseinandersetzen.
|
dann darf ich dir das gute Tutorial von BenBE empfehlen:
Wie man Strings speichert und liest ...
_________________ MfG Lannes
(Nichts ist nicht Nichts) and ('' <> nil ) and (Pointer('') = nil ) and (@('') <> nil )
|
|
rushifell
Beiträge: 306
Erhaltene Danke: 14
|
Verfasst: Fr 11.01.13 18:57
Für XML gibts doch bestimmt gute Libraries!?
catweasel hat folgendes geschrieben: | Aber muss ich da nicht dann noch ein Setlength(DynArray,LengthDynArray); einfügen bevor ich die Daten lese? |
Ich nutze immer einen Buffer mit konstanter Größe, lese vom Stream in den Buffer und übergebe dann die Daten an das Dynamische Array. Sollte wahrscheinlich auch direkt gehen, hatte aber damit schon öfter Probleme.
Bei den Arrays musst Du unbedingt auf den Typ achten:
Delphi-Quelltext 1: 2: 3: 4: 5: 6:
| Var ByteArray:Array[1..2] of Byte; IntegerArray:Array[1..2] of Integer; begin ShowMessage(InttoStr(SizeOf(ByteArray))); ShowMessage(InttoStr(SizeOf(IntegerArray))); end; |
Beim Schreiben und Lesen von Strings solltest Du Dir Gedanken machen, ob Du Unicode unterstützen möchtest (würde ich generell empfehlen).
Ich nutze übrigens gerne die TFastFileStream-Klasse von Flamefire:
www.entwickler-ecke....ewtopic.php?t=100088
Gruß
|
|
jaenicke
Beiträge: 19284
Erhaltene Danke: 1742
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Fr 11.01.13 21:25
Mit einer MMF wie sie auch in der Klasse verwendet wird kann man auch direkt als Array auf die Daten zugreifen. Da Windows diese automatisch in den RAM spiegelt, geht das extrem schnell.
Das Prinzip siehst du in dieser Unit von mir sehr gut, bei der das ganze Stream drum herum noch nicht drin ist. Deshalb ist die zum Verständnis vermutlich besser.
www.entwickler-ecke....ewtopic.php?p=607865
Für diesen Beitrag haben gedankt: rushifell
|
|
catweasel
Beiträge: 487
Erhaltene Danke: 1
Win 7 64bit
Delphi 7 Second Sedition V7.2
|
Verfasst: Sa 12.01.13 12:36
rushifell hat folgendes geschrieben : |
Ich nutze immer einen Buffer mit konstanter Größe, lese vom Stream in den Buffer und übergebe dann die Daten an das Dynamische Array. Sollte wahrscheinlich auch direkt gehen, hatte aber damit schon öfter Probleme.
Gruß |
Hi,
Das mit den FileStreams hab ich das jetzt geblickt
Aber ein Problem hab ich noch:
Ich habe eine Klasse (TBaseClass) geschrieben welche zwei Felder einführt: "Name" und "Description" (beides Strings).
Davon abgeleitet ist TChildClassA, welche zwei zusätzliche Felder "Index" (Int64) und "Color" (TColor) einführt.
Die TBaseClass.LoadFromFile bzw. SaveToFile funktioniert ganz gut wenn ich den FileStream nach dem Lesen/Schreiben in der Prozedur wieder freigebe. (Ansonsten bleibt die Datei ja "in Verwendung").
Jetzt möchte ich TChildClassA auch LoadFromFile/SaveToFile Prozeduren geben. Dazu möchte ich aber die "Lese/Schreib-arbeit" für die Felder von TBaseClass nicht duplizieren.
Ich hatte mir gedacht ich ruf einfach die Parent Prozedur über inherited auf, speichere die zusätzlichen Felder hintendrann. Ob nun TBaseClass oder TChildClassA den "Sack zu" machen muss wollte ich per: if self is TChildClassA then FDiskFile.Free; testen.
Ich bekomme aber nur Fehler. Keine AccessViolation, sondern eine "External Exception". Einzelschritt debugging deutet an das das Freigeben des Streams irgendwie scheitert. Aber nur in TChildClassA....
Ich hab mal das Projekt hier eigefügt.
Wenn man das FDiskFile.Free in TBaseClass nicht auskommentiert, dann funktionierts damit. TBaseClass Objekte lassen sich dann lesen und speichern.
Wo liegt der Fehler
Cheers,
Catweasel
Einloggen, um Attachments anzusehen!
_________________ Pommes werden schneller fertig wenn man sie vor dem Frittieren einige Minuten in siedendes Fett legt.
|
|
jfheins
Beiträge: 918
Erhaltene Danke: 158
Win 10
VS 2013, VS2015
|
Verfasst: Sa 12.01.13 13:51
In der VCL gibt es (fast) immer eine SaveToFile Methode und eine SaveToStream Methode.
Mache es doch genau so: Die SaveToFile öffnet einen Filestream, ruft die andere Methode auf und schließt diesen wieder.
Die SaveToStream Methode kannst du dann überschreiben und die alte mit inherited aufrufen
|
|
catweasel
Beiträge: 487
Erhaltene Danke: 1
Win 7 64bit
Delphi 7 Second Sedition V7.2
|
Verfasst: Sa 12.01.13 13:57
jfheins hat folgendes geschrieben : | In der VCL gibt es (fast) immer eine SaveToFile Methode und eine SaveToStream Methode.
Mache es doch genau so: Die SaveToFile öffnet einen Filestream, ruft die andere Methode auf und schließt diesen wieder.
Die SaveToStream Methode kannst du dann überschreiben und die alte mit inherited aufrufen |
Hmmm....Exakt das habe ich versucht und genau das Schliessen klappt nicht.
Wo liegt der Fehler in meinem Quelltext?
Cheers,
Catweasel
_________________ Pommes werden schneller fertig wenn man sie vor dem Frittieren einige Minuten in siedendes Fett legt.
|
|
jfheins
Beiträge: 918
Erhaltene Danke: 158
Win 10
VS 2013, VS2015
|
Verfasst: Sa 12.01.13 14:24
catweasel hat folgendes geschrieben : |
Hmmm....Exakt das habe ich versucht und genau das Schliessen klappt nicht.
Wo liegt der Fehler in meinem Quelltext? |
Du machst es eben nicht exakt wie ich es vorgeschlagen hatte (SaveTo Stream <> SaveTo File)
Ich finde da nur: Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| procedure TBaseClass.SaveToFile(filename: string); var Len : integer; begin FDiskFile := TFileStream.Create(filename,fmCreate); Len := Length(FName); FDiskFile.Write(Len,SizeOf(Len)); FDiskFile.Write(PChar(FName)^,Len); Len := Length(FDescription); FDiskFile.Write(Len,SizeOf(Len)); FDiskFile.Write(PChar(FDescription)^,Len); if self is TBaseClass then FDiskFile.Free; end; |
Ich habe das hier gemeint: Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
| procedure TBaseClass.SaveToStream(AStream: TStream); var Len : integer; begin Len := Length(FName); AStream.Write(Len,SizeOf(Len)); AStream.Write(PChar(FName)^,Len); Len := Length(FDescription); AStream.Write(Len,SizeOf(Len)); AStream.Write(PChar(FDescription)^,Len); end;
procedure TBaseClass.SaveToFile(filename: string); begin FDiskFile := TFileStream.Create(filename,fmCreate); SaveToStream(FDiskFile); FDiskFile.Free; end; |
Die SaveToStream-Methode kannst du dann in der Kind-Klasse überschrieben und aufrufen.
|
|
catweasel
Beiträge: 487
Erhaltene Danke: 1
Win 7 64bit
Delphi 7 Second Sedition V7.2
|
Verfasst: Sa 12.01.13 14:44
jfheins hat folgendes geschrieben : | catweasel hat folgendes geschrieben : |
Hmmm....Exakt das habe ich versucht und genau das Schliessen klappt nicht.
Wo liegt der Fehler in meinem Quelltext? |
Du machst es eben nicht exakt wie ich es vorgeschlagen hatte (SaveToStream <> SaveToFile)
Ich finde da nur: Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| procedure TBaseClass.SaveToFile(filename: string); var Len : integer; begin FDiskFile := TFileStream.Create(filename,fmCreate); Len := Length(FName); FDiskFile.Write(Len,SizeOf(Len)); FDiskFile.Write(PChar(FName)^,Len); Len := Length(FDescription); FDiskFile.Write(Len,SizeOf(Len)); FDiskFile.Write(PChar(FDescription)^,Len); if self is TBaseClass then FDiskFile.Free; end; |
Ich habe das hier gemeint: Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
| procedure TBaseClass.SaveToStream(AStream: TStream); var Len : integer; begin Len := Length(FName); AStream.Write(Len,SizeOf(Len)); AStream.Write(PChar(FName)^,Len); Len := Length(FDescription); AStream.Write(Len,SizeOf(Len)); AStream.Write(PChar(FDescription)^,Len); end;
procedure TBaseClass.SaveToFile(filename: string); begin FDiskFile := TFileStream.Create(filename,fmCreate); SaveToStream(FDiskFile); FDiskFile.Free; end; |
Die SaveToStream-Methode kannst du dann in der Kind-Klasse überschrieben und aufrufen. |
Ah Ok
Ich werd das heute Abend mal testen.
Hab ich das richtig verstanden: Die SaveToFile Prozedur ist nur in der BAsisKlasse implementiert und die jeweilige KindKlasse ruft als erstes inherited SaveToStream(AStream) auf?
Warum muss AStream hier nicht als var Parameter übergeben werden?
So etwa: procedure TBaseClass.SaveToStream(var AStream:TStream);
Und dann könnte FDiskFile in der SaveToFile Prozedur auch eine lokale Variable sein. Dann brauche ich gar kein FileStream als Klassenfeld?
Mit dem Überschreiben von Prozeduren hab ich noch nicht so viel Erfahrung (mach ich sonst kaum).
Danke für die Tips
Cheers,
Catweasel
_________________ Pommes werden schneller fertig wenn man sie vor dem Frittieren einige Minuten in siedendes Fett legt.
|
|
WasWeißDennIch
Beiträge: 653
Erhaltene Danke: 160
|
Verfasst: Sa 12.01.13 14:48
Objektinstanzen muss man i.d.R. nicht als Var-Parameter übergeben, da es sich intern um Pointer handelt. Und in den allermeisten Fällen möchte man ja nicht die Instanz an sich ändern, sondern deren Properties.
|
|
catweasel
Beiträge: 487
Erhaltene Danke: 1
Win 7 64bit
Delphi 7 Second Sedition V7.2
|
Verfasst: So 13.01.13 14:07
Hi,
vielen Dank an Alle die mit ihren tollen Tips geholfen haben. Echt Super
Schaut euch aber nch mal den Sourcecode an und sagt mir ob es da noch formal etwas zu verbessern gibt.
Irgendein Feld oder eine Methode die öffentlicher ist als nötig?
Cheers,
Catweasel
Einloggen, um Attachments anzusehen!
_________________ Pommes werden schneller fertig wenn man sie vor dem Frittieren einige Minuten in siedendes Fett legt.
|
|
Keldorn
Beiträge: 2266
Erhaltene Danke: 4
Vista
D6 Prof, D 2005 Pro, D2007 Pro, DelphiXE2 Pro
|
Verfasst: So 13.01.13 19:07
Hallo
würde das ganze noch um die TWriter und TReader ergänzen
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
| procedure TBaseClass.SaveToStream(AStream: TStream); Var Writer:Twriter; begin writer:= Twriter.create(AStream,4096); try begin writer.writeInteger(aktuelleVersionsNummer); writer.writeString(...); writer.writeBoolean(...); writer.writefloat(...); writer.writeDate(...); end; finally writer.free; end; end; |
auslesen mit Treader genau andersrum.
Brauchst halt nicht für jeden Datentyp dir extra was einfallen lassen, das ist schon da.
Außerdem ließt es sich meiner Meinung nach besser, da es alles Einzeiler sind. Es wird wahrscheinlich auch zukunftssicherer sein, grade, wenn Du strings schreibst.
Gruß Frank
_________________ Lükes Grundlage der Programmierung: Es wird nicht funktionieren.
(Murphy)
|
|
catweasel
Beiträge: 487
Erhaltene Danke: 1
Win 7 64bit
Delphi 7 Second Sedition V7.2
|
Verfasst: So 13.01.13 19:36
Keldorn hat folgendes geschrieben : | Hallo
würde das ganze noch um die TWriter und TReader ergänzen
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
| procedure TBaseClass.SaveToStream(AStream: TStream); Var Writer:Twriter; begin writer:= Twriter.create(AStream,4096); try begin writer.writeInteger(aktuelleVersionsNummer); writer.writeString(...); writer.writeBoolean(...); writer.writefloat(...); writer.writeDate(...); end; finally writer.free; end; end; |
auslesen mit Treader genau andersrum.
Brauchst halt nicht für jeden Datentyp dir extra was einfallen lassen, das ist schon da.
Außerdem ließt es sich meiner Meinung nach besser, da es alles Einzeiler sind. Es wird wahrscheinlich auch zukunftssicherer sein, grade, wenn Du strings schreibst.
Gruß Frank |
Wow. TReader/TWriter kannte ich auch noch nicht. Ich hab da mal ne Frage zu. Als ich auf der Embacaero Website mir ein paar Sachen dazu durchgelesen habe: docwiki.embarcadero....stem.Classes.TWriter bin ich etwas verwirrt. Dort steht das man TReader/TWriter nicht direkt erzeugen soll weil das sowieso schon Bestandteil der TSTream Klasse ist.
Hab ich das was falsch verstanden? Ich finde uach kaum "Tutorials" zu TREader/TWriter...
Und was ist die Obergrenze für die Buffergröße?. Hab dazu auch nichts gefunden.
Cheers,
Catweasel
_________________ Pommes werden schneller fertig wenn man sie vor dem Frittieren einige Minuten in siedendes Fett legt.
|
|