Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Daten-Struktur für Backup-Tool


Flamefire - Do 11.09.08 11:24
Titel: Daten-Struktur für Backup-Tool
Hallo.
ich möchte ein kleines Backup-Tool machen. Dafür lese ich vor dem Vergleichen alle Dateien und Ordner in eine Struktur ein:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
TOrdner=record
  name:String;
  Ordner:Array of TOrdner;
  Dateien:Array of String;
  back:POrdner;
end;
POrdner=^TOrdner;


Sollte an sich selbsterklärend sein.

Jetzt die Frage: Gibt es bessere Lösungen betreffs Speicher und Performance?

Wäre für alle Hinweise auch zu anderen Bereichen eines Backup-Tools dankbar.


Narses - Do 11.09.08 11:36

Moin!

Ich würde es mit Objekten und dem TObjectList-Container machen, um mir die Speicherverwaltung vom Hals zu halten. :nixweiss:

cu
Narses


Flamefire - Do 11.09.08 12:40

Hmm...und das soll schneller sein?
Ich mag sonst keine alzu fertigen Allround-Bausteine, da die meist eher Performance-Verlust bringen.
Diese Struktur lässt sich wunderbar durch rekurive prozeduren erstellen, bearbeiten und löschen.
Mit dem ObjectList habe ich noch nie gearbeitet...Ist der echte besser? Was kann der und wie benutzt man den?

BTW: Bei dem Array of String: Reicht da ein Setlength(Dateien,0); oder muss ich jeden String einzeln löschen?


Gausi - Do 11.09.08 12:48

Ich glaube, Narses meinte das in etwa so:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
TOrdner = class
  name: String; 
  Ordner: TObjectList; // Mit TOrdnern drin 
  Dateien: TStringlist; // bequemer als Array of String, im Prinzip aber dasselbe ;-)
  back: TOrdner; 
end;


Der Vorteil ist, dass TObjectlist recht schöne Methoden zum Einfügen und löschen bereitstellt, die du mit deiner Record-Array-Struktur alle selber bauen müsstest.

Zur Geschwindigkeit: Der Aufbau der Objekte im Speicher ist eh nicht der Flaschenhals so einer Anwendung. Der liegt eher im Ermitteln der Daten. ;-)


baka0815 - Do 11.09.08 12:50

Nimm lieber 'ne THashedStringList (unit IniFiles), die ist bedeutend schneller als die normale TStringList!


Flamefire - Do 11.09.08 12:54

ja ich weis dass das ermitteln schon lange dauert..aber wenn ich sagen wir 500.000 Dateien und 100.000 Ordner habe und für jeden Ordner statt nur den Pointer auf die Struktur ebn das ganze Zusätzliche Verwaltungszeug dran hängt, dass sich das sowohl in Geschwindigkeit als auch beim Speicher bemerkbar macht...
Obwohl Speicher: 600.000x~50Bytes=3MB
Geht also...

Und mir würde es mehr helfen, zu erfahren, warum das besser sein soll...Ich glaube nix was ich nicht verstehe ^^
Warum solln die TObjects besser sein als n Array?
Warum ist THashedStringList schneller als TStringList?


baka0815 - Do 11.09.08 12:58

user profile iconFlamefire hat folgendes geschrieben:
Warum ist THashedStringList schneller als TStringList?

Die THashedStringList macht das, was der Name schon sagt. Es werden Hashes über die Namen/Einträge gebildet und somit ist der Zugriff deutlich performanter als bei einer normalen TStringList.

Kann jedoch sein, dass das Einfügen in die Liste (aufgrund der Hash-Bildung) etwas langsamer ist, da kommt's dann auf den Fall genau an. Aber bei mehreren Hundert Dateien in einem Verzeichnis kann sich das positiv auswirken.


Gausi - Do 11.09.08 13:01

TObjectlist ist ein Array. Der Vorteil liegt daran, dass der ganze Bereich um die Speicherverwaltung (Objekte/Records erzeugen, einfügen, verschieben, löschen usw) über Methoden dieser Klasse erledigt werden kann. Und da wird mit einigen Tricks gearbeitet (Stichwort: Capacity), so dass das auch recht performant geschieht.


Flamefire - Do 11.09.08 13:17

ok...damit ich mich erst durch bergeweise dokumentationen arbeiten muss, kannst du mir n ganz einfaches bsp geben, wie ich das verwende?
also deklarationen
einfügen
zugriff
löschen


wulfskin - Do 11.09.08 13:42

Hallo Flammenfeuer,

tonnenweise Dokumentation? Nicht übertreiben!

Im Grunde genommen brauchst du nur die Methode Add und Delete und die Eigenschaft Items. Hier ein kurzes Beispiel:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
var List: TObjectList;

//Test ist ein Objekt

//irgendwo
  List.Add(Test);

//irgendwo anders
  List.Delete(List.IndexOf(Test));
  Test.Free;

//alle Objekte durchgehen
var I: Integer;
begin
  for I := 0 to List.Count - 1 do
    MachWas(List[I]);
end;
Hab schon lange nicht mehr mit Delphi gearbeitet, deswegen kann ich nur hoffen das die Methoden-Namen noch stimmen.

Viel Glück,
Hape


Flamefire - Do 11.09.08 14:04

ok danke...
und "Test" kann ein beliebiges record sein?

warum sollte nu aber der zugriff über hashstringlist schneller sein?
wegen den hashes denke ich ist das nur wenn man einen eintrag sucht...nicht aber wenn man das alles nur über die (numerischen) indizes erledigt


Reinhard Kern - Do 11.09.08 17:48
Titel: Re: Daten-Struktur für Backup-Tool
user profile iconFlamefire hat folgendes geschrieben:
Hallo.
ich möchte ein kleines Backup-Tool machen. Dafür lese ich vor dem Vergleichen alle Dateien und Ordner in eine Struktur ein:
....


Hallo,

ich weiss ja nicht, was dein Backup-Tool alles können soll, aber das Einlesen der zu sichernden Struktur ist nicht unbedingt notwendig. Viele professionelle Tools arbeiten so, andere klappern aber einfach die Ordnerstruktur während des Backups ab. Dann kann man zwar nicht bzw. nicht ohne weiteres eine Fortschrittsanzeige machen, aber die Anzeige der bereits gesicherten Dateien und MBytes hilft auch schon.

Für das Restore gilt das gleiche: man speichert einfach Datei für Datei zurück, dann braucht man keinen Katalog.

Gruss Reinhard


Flamefire - Do 11.09.08 18:55

es geht um ein tool, das daten abgleichen soll...sprich: Datei fehlt, ist neu, ist geändert etc...
darum muss ich erst alles einlesen...
selbst wenn das nicht nötig sein sollte, finde ich es so besser...

EDIT: Habe ein Problem

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
type
  POrdner=^TOrdner;
  TOrdner = class
    name: String;
    Ordner: TObjectList; // Mit TOrdnern drin
    Dateien: TStringlist; // bequemer als Array of String, im Prinzip aber dasselbe ;-)
    back: POrdner;
  end;

...

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
procedure delOrdner(var akt:TOrdner);
var i:Integer;
begin
  For i:=0 to akt.Ordner.Count-1 do
    delOrdner(TOrdner(akt.Ordner[i]));
  akt.Ordner.Free;
  akt.Dateien.Free;
  akt.name:='';
  akt.Free;
end;

procedure newOrdner(var akt:TOrdner;name:String='';last:POrdner=nil);
begin
  akt:=TOrdner.Create;
  akt.name:=name;
  akt.Dateien:=TStringList.Create;
  akt.Ordner:=TObjectlist.Create;
  akt.back:=last;
  if(last<>nil) then last^.Ordner.Add(akt);
end;


newOrdner() funktioniert wundebar
aber das rekursive löschen geht nicht
kriege die Meldung: Konstantenobjekt kann nicht als var parameter übergeben werden

wie kann ich das nun machen?


Boldar - Do 11.09.08 21:27

Dann übergebe halt ein Pordner als Parameter!


Flamefire - Do 11.09.08 21:33

schon versucht mit

Delphi-Quelltext
1:
2:
3:
4:
5:
procedure delOrdner(akt:POrdner);
var i:Integer;
begin
  For i:=0 to akt^.Ordner.Count-1 do
    delOrdner(POrdner(@akt^.Ordner[i]));


Ebenfalls ne Fehlermeldung: "Variable erwartet"


Narses - Do 11.09.08 21:35

Moin!

user profile iconFlamefire hat folgendes geschrieben:
Habe ein Problem
Stimmt ;) du solltest dringend ein paar OOP-Tutorials lesen und dich mit der VCL-Doku beschäftigen. :|


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
type
  //POrdner=^TOrdner; -> das ist Unfug! TOrdner ist schon ein Zeiger.
  TOrdner = class
    name: String;
    Ordner: TObjectList; // Mit TOrdnern drin
    Dateien: TStringlist; // bequemer als Array of String, im Prinzip aber dasselbe ;-)
    back: TOrdner; // eigentlich ist eine Rückwärtsverkettung bei dem
                   // Ansatz TObject/TObjectList unnötig; aber wenn schon, dann richtig.
  end;



Delphi-Quelltext
1:
2:
3:
procedure delOrdner(var akt:TOrdner);
//...
procedure newOrdner(var akt:TOrdner;name:String='';last:POrdner=nil);
Du brauchst einen vernünftigen Konstruktor und Destruktor der Klasse TOrdner, dann erledigt sich das Freigeben von ganz alleine, weil die TObjectList ihren Inhalt beim Freigeben mit entsorgt, wenn .OwnsObjects = TRUE ist (default).

Fazit: du mischt hier record/Pointer mit OOP, das wird nix. :?

cu
Narses


Flamefire - Do 11.09.08 22:02

gut dann habe ich tatsächlich keine Ahnung...
warum sollte sich die objectlist selber entsorgen und das noch rekursiv?
das muss ich doch alles selbst machen...woher soll der denn wissen was er machen soll?
wie müsste der constructor/destructor denn aussehen?

Die Rückrefernezierung ist dafür nötig, dass ich notfalls aus einem Eintrag den Kompletten Pfad wiederherstellen kann.

Und genau das meinte ich: Ich habe echt keinen Bock, mich durch mehr Tutorials und Dokus zu lesen, als mann verkraften kann. Man liest ne ewig lange Doku auf der Suche nach ner Kleinigkeit und stellt dann fest, dass es die falsche war...
Mit Klassen habe ich vorher selten gearbeitet...
Darum wäre es sinnvoller, einfach mal die Mini-Info zu geben als den Hinweis auf die Doku...

Meine 2 Prozeduren sind doch schon Konstruktoren/Destruktoren, nur dass ich die ebn nicht so gekapselt habe (in Create/Destroy)

Und wenn TOrdner schon ein Zeiger ist, warum muss ich den dann noch als referenz-Parameter übergeben?
Das ist ja bei Zeigern nicht nötig...


Narses - Do 11.09.08 22:08

Moin!

user profile iconFlamefire hat folgendes geschrieben:
Und genau das meinte ich: Ich habe echt keinen Bock, mich durch mehr Tutorials und Dokus zu lesen, als mann verkraften kann.
Wenn ich´s mir recht überlege :? ignorier meine Beiträge und bleib mal schön bei pointer/record... ;) mit weniger als kostenlosem Privatunterricht darf man dem Herrn aber nicht kommen, was? "nene, so nicht, es muss schon schön auf mich zugeschnitten sein"

Viel Erfolg
Narses


Flamefire - Do 11.09.08 22:13

ok sry...werds mir mal angucken und dann posten...sag dann einfach obs richtig ist...


Narses - Do 11.09.08 22:16

Moin!

user profile iconFlamefire hat folgendes geschrieben:
ok sry...werds mir mal angucken und dann posten...sag dann einfach obs richtig ist...
Hey, das hätte ich von dir nicht (noch nicht/mehr) erwartet! :zustimm: Respekt. ;)

Ich werd´s mir dann anschauen.

cu
Narses


Flamefire - Do 11.09.08 22:26

nja bin z.zt. n bissl gestresst...sry wenn ich überreagiert habe...
Hab mal drüber nachgedacht:


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:
24:
25:
26:
27:
28:
29:
30:
31:
type
  POrdner=^TOrdner;
  TOrdner = class(TObject)
    name: String;
    Ordner: TObjectList; // Mit TOrdnern drin
    Dateien: TStringlist; // bequemer als Array of String, im Prinzip aber dasselbe ;-)
    back: POrdner;

    public
      constructor Create(sname:String='';last:POrdner=nil);
      destructor Destroy; override;
  end;
...
destructor TOrdner.Destroy;
var i:Integer;
begin
  Ordner.Free;
  Dateien.Free;
  name:='';
  inherited Destroy;
end;

constructor TOrdner.Create(sname:String='';last:POrdner=nil);
begin
  inherited Create;
  name:=sname;
  Dateien:=TStringList.Create;
  Ordner:=TObjectlist.Create;
  back:=last;
  if(last<>nil) then last^.Ordner.Add(self);
end;


So weit OK?

Wenn Ich richtig liege wird bei "Ordner.Free;" für alle Ordner in der Liste "Free" aufgerufen und damit ist das schon rekursiv. Richtig?


Narses - Do 11.09.08 22:39

Moin!

user profile iconFlamefire hat folgendes geschrieben:
Hab mal drüber nachgedacht:
Na also, geht doch. ;)

Ich würde es aber trotzdem so ansetzen:

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:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
type
  TOrdner = class(TObject)
  private
    FOwner: TOrdner;
    FName: String;
    FOrdner: TObjectList;
    FDateien: TStringList;
  public
    constructor Create(const AName: String = ''; AOwner: TOrdner = NIL);
    destructor Destroy; override;
    property Owner: TOrdner read FOwner;
    property Name: String read FName write FName;
    property Ordner: TObjectList read FOrdner;
    property Dateien: TStringList read FDateien;
  end;

implementation

constructor TOrdner.Create(const AName: String = ''; AOwner: TOrdner = NIL);
begin
  inherited Create;
  FName := AName;
  FOwner := AOwner;
  FOrdner := TObjectlist.Create;
  FDateien := TStringList.Create;
  if Assigned(FOwner) then
    FOwner.Ordner.Add(Self);
end;

destructor TOrdner.Destroy;
begin
  FreeAndNil(FDateien);
  FreeAndNil(FOrdner);
  inherited;
end;
Um einen solchen Baum freizugeben reicht es dann, den ersten Root-Ordner mit .Free freizugeben. Allerdings ist zu beachten, dass beim Löschen von einzelnen Unterordnern über eine (externe) Objekt-Referenz trotzdem ein Eintrag in der FOrdner-Objectlist des Owners verbleibt. Deshalb also besser immer über die Methoden der enthaltenden TObjectList löschen (.Delete oder .Remove). :idea:

user profile iconFlamefire hat folgendes geschrieben:
Wenn Ich richtig liege wird bei "Ordner.Free;" für alle Ordner in der Liste "Free" aufgerufen und damit ist das schon rekursiv. Richtig?
Exakt, darauf läuft es hinaus. :)

cu
Narses


Flamefire - Do 11.09.08 22:59

Ok sehr schön sauber so =)

user profile iconNarses hat folgendes geschrieben:

Allerdings ist zu beachten, dass beim Löschen von einzelnen Unterordnern über eine (externe) Objekt-Referenz trotzdem ein Eintrag in der FOrdner-Objectlist des Owners verbleibt. Deshalb also besser immer über die Methoden der enthaltenden TObjectList löschen (.Delete oder .Remove). :idea:


Du meinst damit statt: UnterOrdner.Free;
besser:

Delphi-Quelltext
1:
UnterOrdner.Owner.Ordner[i].Free;                    


Würde da das hier gehen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
destructor TOrdner.Destroy;
var tmp:TOrdner;
begin
  tmp:=FOwner;
  if(tmp<>nil) then begin
    FOwner:=nil;
    tmp.Ordner.Remove(self)
  end else begin
    FreeAndNil(FDateien);
    FreeAndNil(FOrdner);
    inherited;
  end;
end;

Oder führt das möglichweise zur Zugriffsverletzung?

EDIT: Getestet: Logischerweise Zugriffsverletzung...Gibt es ne andre Möglichkeit? Sowas wie dass er die Zeile "tmp.Ordner.Remove(self)" in ne art warteliste stellt und ausführt, sobald er aus Destroy zurückkehrt?


Narses - Do 11.09.08 23:11

Moin!

user profile iconFlamefire hat folgendes geschrieben:
Du meinst damit statt: UnterOrdner.Free;
besser:

Delphi-Quelltext
1:
UnterOrdner.Owner.Ordner[i].Free;                    
Nein, so:

Delphi-Quelltext
1:
2:
UnterOrdner.Owner.Ordner.Extract(Unterordner);
UnterOrdner.Free;
oder gleich:

Delphi-Quelltext
1:
UnterOrdner.Owner.Ordner.Remove(Unterordner);                    
Wobei hier sicherzustellen ist, dass Owner auch nicht NIL ist!

user profile iconFlamefire hat folgendes geschrieben:
Würde da das hier gehen:
Nein, das inherited Destroy; muss auf jeden Fall ausgeführt werden. Du könntest höchstens mit einem if Assigned(FOwner) als erste Anweisung auf einen solchen reagieren und dich dann dort austragen lassen (macht die VCL bei den Components auch so). Allerdings musst du dann die beiden Fälle unterscheiden: a) einzelnes Objekt "ausnahmsweise" freigeben und b) einen Objektbaum abhacken (z.B. mit .Destroying: Boolean oder so machbar). Ich würde das aber lassen und ggfs. manuell die Referenz entfernen. ;)

cu
Narses


Flamefire - Do 11.09.08 23:31

Perfekt. Das Extrakt ist es:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
destructor TOrdner.Destroy;
begin
  If(FOwner<>nil) then FOwner.FOrdner.Extract(self);
  FreeAndNil(FDateien);
  FreeAndNil(FOrdner);
  inherited;
end;


EDIT: Ist aber ineffizient, da die Suche und das Entfernen auch gemacht wird, wenn der ganze Baum gelöscht wird
Besser:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
destructor TOrdner.Destroy;
begin
  FreeAndNil(FDateien);
  FreeAndNil(FOrdner);
  inherited;
end;
...
procedure TOrdner.Free(ARemove:Boolean=false);
begin
  If(self<>nil) then begin
    If ARemove and (FOwner<>nil) then FOwner.FOrdner.Extract(self);
    Destroy;
  end;
end;


Kommt zwar ne Warnmeldung von Delphi aber das sollte so gehen. Oder hab ich was übersehen?