![]() |
Delphi-Version: 2009
Record mit dyn.Array speichern
Hallo zusammen!
Wie speichert man am besten ein Record, welches ein dyn. Array enthält? "Normale" Records kann man ja relativ bequem mit file of Record speichern, jedoch nur, wenn das Record eine feste Größe hat. Wie ich ein (einzelnes) Array in einem Stream speichern kann weiß ich in etwa (Anzahl der Elemente speichern, dann die Elemente ... usw). So könnte ich zwar das ganze Record speichern, ist bei den vielen Eigenschaften jedoch etwas umständlich. Deshalb frage ich mich, ob es nicht eine kürzere Möglichkeit gibt, diese beiden Speichermethoden irgendwie zu verbinden (also das ganze Record samt Array in eine Datei/Stream). Das Record besteht aus (z.Z.) 30 Eigenschaften und eben 1 dyn. Array. |
AW: Record mit dyn.Array speichern
Der einzige Ansatz der mir auf die Schnelle einfällt wäre es, das Array als letztes im Record zu deklarieren und dann mit einem Schreibbefehl alle vorherigen Einträge auf einmal zu schreiben, indem man von Anfang des Arrays bis ein Byte vor der Adresse des Arrays schreibt.
|
AW: Record mit dyn.Array speichern
Theoretisch, indem du den dynamischen Teil manuell speicherst.
Am Besten speicherst du diesen Record aber garnicht direkt, also jedenfalls nicht den Teil, wo das dyn. Array liegt. Denn wenn du diesen Record wieder laden würdest, würdest DU die automatische Speicherverwaltung des Arrays zerschießen. |
AW: Record mit dyn.Array speichern
Wie meinst du das, den dynamischen Teil manuell speichern? So ähnlich wie ich es beschrieben habe? Es wäre auch möglich, alle Records(ohne den dynamischen Teil) in einer Datei zu speichern und alle arrays in einer anderen Datei. Nur das Problem ist nach wie vor: wie verlinke ich diese Informationen dann, also dass in dem Record dann (eventuell in einer neuen Eigenschaft) irgendwie hinterlegt ist, wo sich das zugehörige Array befindet.
Zitat:
|
AW: Record mit dyn.Array speichern
Delphi-Quellcode:
Laden geht genauso (analog). Wenn Du das as Recordmethoden implementierst, sieht das doch hübsch aus.
Type
TMyRecord = Record A,B,C,D : TSomeType; E : TDynArray; Procedure SaveToStream (aStream : TStream); End; Procedure TMyRecord.SaveToStream(aStream : TStream); Var numberOFixedBytes, elementCount, firstElement : Integer; Begin // statischer/fester Teil numberOFixedBytes := PChar(Integer(@E)-Integer(@A)); aStream.Write(A,NumberOfFixedBytes); // Für jedes dynamische Array folgenden Code ausführen elementCount := Length(E); aStream.Write(elementCount); if elementCount>0 then begin firstElement := Low(E); aStream.Write(E[firstElement], elementCount*SizeOf(E[firstElement])); end End; |
AW: Record mit dyn.Array speichern
Das ist genau das, was ich meinte.
Aber das ist auch keine wirklich saubere Art um ein record zu speichern. Ich selber verwende eigentlich immer einen class-helper für TStream mit Methoden wie WriteInteger und ReadInteger, welche ich dann für jeden Stream nutzen kann. Ich werde gleich mal ein Beispiel posten. Leider geht das gerade nicht gut, da ich mobil online bin. MFG |
AW: Record mit dyn.Array speichern
So nun mal richtig.
Dies ist der Stream-helper den ich immer verwende:
Delphi-Quellcode:
Zum speichern eines Records verwende ich dann folgende Implementation:
TStreamHelper = class helper for TStream
function ReadBoolean: Boolean; procedure WriteBoolean(v: Boolean); Function ReadByte : Byte; Procedure WriteByte(v : Byte); Function ReadWord : Word; Procedure WriteWord(v : Word); function ReadInteger: Integer; procedure WriteInteger(v: Integer); function ReadCardinal: Cardinal; procedure WriteCardinal(v: Cardinal); function ReadInt64: Int64; procedure WriteInt64(v: Int64); function ReadSingle: Single; procedure WriteSingle(v: Single); function ReadDouble: Double; procedure WriteDouble(v: Double); function ReadString: AnsiString; procedure WriteString(const v: AnsiString); function ReadChars(count: Integer): AnsiString; procedure WriteChars(const v: AnsiString; count: Integer); function ReadWideString: WideString; procedure WriteWideString(const v: WideString); function ReadWideChars(count: Integer): WideString; procedure WriteWideChars(const v: WideString; count: Integer); function StreamMove(Src, Dst, Count: Int64): Int64; end; implementation {$ifdef VER140}{$REGION 'TStreamHelper'}{$endif} function TStreamHelper.StreamMove(Src, Dst, Count: Int64): Int64; var Buffer : Array [0..1024 * 32 - 1] of Byte; ByteCount : Integer; ReadBytes : Integer; begin Result := 0; while Count > 0 do begin If Count > length(Buffer) then ByteCount := length(Buffer) else ByteCount := Count; Position := Src; ReadBytes := Read(Buffer, ByteCount); If ReadBytes <> ByteCount then raise Exception.Create('Abnormal exception. Could not read desired bytes from the file.'); Position := Dst; ByteCount := Write(Buffer, ReadBytes); If ByteCount <> ReadBytes then raise Exception.Create('Abnormal exception. Could not write desired bytes to the file.'); Count := Count - ByteCount; Result := Result + ByteCount; Src := Src + ByteCount; Dst := Dst + ByteCount; end; End; Function TStreamHelper.ReadBoolean : Boolean; Begin Read(Result, 1); End; Procedure TStreamHelper.WriteBoolean(v : Boolean); Begin Write(v, 1); End; Function TStreamHelper.ReadByte : Byte; Begin Read(Result, 1); End; Procedure TStreamHelper.WriteByte(v : Byte); Begin Write(v, 1); End; Function TStreamHelper.ReadWord : Word; Begin Read(Result, 2); End; Procedure TStreamHelper.WriteWord(v : Word); Begin Write(v, 2); End; Function TStreamHelper.ReadInteger : Integer; Begin Read(Result, 4); End; Procedure TStreamHelper.WriteInteger(v : Integer); Begin Write(v, 4); End; Function TStreamHelper.ReadCardinal : Cardinal; Begin Read(Result, 4); End; Procedure TStreamHelper.WriteCardinal(v : Cardinal); Begin Write(v, 4); End; function TStreamHelper.ReadInt64: Int64; begin Read(Result, 8); end; procedure TStreamHelper.WriteInt64(v: Int64); begin Write(v, 8); end; Function TStreamHelper.ReadSingle : Single; Begin Read(Result, 4); End; Procedure TStreamHelper.WriteSingle(v : Single); Begin Write(v, 4); End; function TStreamHelper.ReadDouble: Double; begin Read(Result, 8); end; procedure TStreamHelper.WriteDouble(v: Double); begin Write(v, 8); end; Procedure TStreamHelper.WriteString(Const v : AnsiString); Var Len : Integer; Begin Len := Length(v); Write(Len, SizeOf(Len)); Write(PChar(v)^, Len); End; Function TStreamHelper.ReadString: AnsiString; Var Len : Integer; Begin Read(Len, SizeOf(Len)); //If len > 20000 Then exit; SetLength(Result, Len); Read(PChar(Result)^, Len); End; Procedure TStreamHelper.WriteChars(Const v : AnsiString; count: Integer); Begin Write(PAnsiChar(v)^, count); End; Function TStreamHelper.ReadChars(count: Integer) : AnsiString; Begin SetLength(Result, count); Read(PAnsiChar(Result)^, count); End; Procedure TStreamHelper.WriteWideString(Const v : WideString); Var Len : Integer; Begin Len := Length(v); Write(Len, SizeOf(Len)); Write(PWideChar(v)^, Len * 2); End; Function TStreamHelper.ReadWideString: WideString; Var Len : Integer; Begin Read(Len, SizeOf(Len)); //If len > 20000 Then exit; SetLength(Result, Len); Read(PWideChar(Result)^, Len * 2); End; Procedure TStreamHelper.WriteWideChars(Const v : WideString; count: Integer); Begin Write(PWideChar(v)^, count * 2); End; Function TStreamHelper.ReadWideChars(count: Integer) : WideString; Begin SetLength(Result, count); Read(PWideChar(Result)^, count * 2); End; {$ifdef VER140}{$ENDREGION}{$endif}
Delphi-Quellcode:
Das sieht nicht nur um einiges ordentlicher aus, sondern ist auch noch schnell programmiert und viel sicherer.
PTestData = ^TTestData;
TTestData = record MyInteger : Integer; MyString : AnsiString; MyWString : WideString; MyIntArray : Array of Integer; MyRecordArray : Array of PTestData; procedure SaveToStream(Stream: TStream); end; implementation procedure TTestData.SaveToStream(Stream: TStream); var i: Integer; begin Stream.WriteInteger(MyInteger); Stream.WriteAString(MyString); Stream.WriteWString(MyWString); Stream.WriteInteger(length(MyIntArray)); Stream.Write(MyIntArray[0], length(MyIntArray) * SizeOf(MyIntArray[0])); Stream.WriteInteger(length(MyRecordArray)); for i := 0 to high(MyRecordArray) do MyRecordArray[i]^.SaveToStream(Stream); end; procedure TTestData.LoadFromStream(Stream: TStream); var i: Integer; begin MyInteger := Stream.ReadInteger; MyString := Stream.ReadAString; MyWString := Stream.ReadWString; SetLength(MyIntArray, Stream.ReadInteger); Stream.Read(MyIntArray[0], length(MyIntArray) * SizeOf(MyIntArray[0])); SetLength(MyRecordArray, Stream.ReadInteger); for i := 0 to high(MyRecordArray) do MyRecordArray[i]^.LoadFromStream(Stream); end; |
AW: Record mit dyn.Array speichern
Hallo,
würde ich genauso machen. Den classhelper würdest Du noch nicht mal unbedingt benötigen, Delphi hat das schon fast alles onboard (Twriter/Treader). Persönlich würde ich auch die unterrecords nach der gleichen Methode, allerdings jedes Element dann einzeln in den stream schreiben. bsp:
Delphi-Quellcode:
Ich finde das recht angenehm, wenn man das dann als Klasse macht und jede Klasse eine savetostream und loadfromstream- Methode hat.
Var Writer:Twriter;
begin writer:= Twriter.create(Stream,4096); try begin //version schreiben Writer.writeinteger(aktuelleVersionsNummer); Writer.WriteString(blub1); Writer.WriteString(blub2); Writer.WriteInteger(blubbi); ... Writer.WriteInteger(Länge des dyn. arrays); for i:=0 to ... usw Writer.writestring(meinrecord.xyz ... end; finally writer.free; end; Die Versionsnummer lege in einer unit fortlaufen ab. Bei savetostream wird immer die aktuelle Version geschrieben und dann kann man die bei loadfromstream auch auswerten
Delphi-Quellcode:
blub2 ist halt irgendwann mal dazugekommen. Auf diese Weise kann man schnell eine Abwärtskompatibiliät erreichen, egal mit welcher alten Version des Programms die Daten gespeichert wurden, die neue bekommt sie immer wieder+korrekt auf. Und ohne, daß Du dir beim Programmieren da große Kopfzerbrechen bereitest.
with reader do
begin //Version lesen Datenversion := readinteger; blub:=Readstring; if Datenversion>=Version155 then begin blub2:=readstring; end; blubbi:=readinteger; end; Wenn das dyn. Array komplett geschrieben wird, würde ja hier bei Änderungen ein recht hoher Aufwand entstehen, alte gespeicherte Daten noch zu lesen und umzuwandeln. Lieber da gleich etwas mehr Code schreiben (der trotzdem sehr übersichtlich und verständlich ist), als dann bei Änderungen eine code zu haben, wo keiner mehr durchsieht. Andere Frage: würde man sich nicht hier auch evtl Zukunftsprobleme einhandeln, wenn die Daten komplett geschrieben werden und man auf die Datentypen nicht aufpaßt (z.B. string verwendet)? Die Treader und Twriter sollten da ja entsprechend aufgebaut sein, daß hier nix in die Hose geht. ![]() ![]() Gruß Frank |
AW: Record mit dyn.Array speichern
Ich würde das auch nicht so krank machen, wie von mir gepostet. Aber es ging um die optimale Möglichkeit, wenn dynamische Arrays im Spiel sind.
Nebenbei würde ich die Versionierung über eine Classfactory lösen, ansonsten sieht die Leseroutine nach einigen Versionen verdammt krank aus und wird im Laufe der Jahre immer schlümmer. |
AW: Record mit dyn.Array speichern
Ok, schon mal danke für die Vorschläge, werde mir das Ganze in den nächsten Tagen mal anschauen.
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 08:45 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz