![]() |
AW: Optimaler Hash-Algorithmus und Strategie für Dateivergleiche, Verzeichnisbaum
Ich möchte deine Aufmerksamkeit auf
![]() ![]() ![]() Ich fand es auch günstig, zunächst nur den Anfang zweier Dateien zu vergleichen, insbesondere bei sehr großen Dateien. Da unter Windows immer mindestens 256 KB eingelesen werden, lese und vergleiche ich zunächst einmal diese ersten 256 KB, was schon einmal so gut wie alle nicht gleichen Dateien aussieben sollte. Bei mir sind Hardlinks ein Thema, weswegen ich auch mittels
Delphi-Quellcode:
die FileID ermittle und vergleiche.
GetFileInformationByHandle
Mir haben auch die Hinweise von ![]() ![]() |
AW: Optimaler Hash-Algorithmus und Strategie für Dateivergleiche, Verzeichnisbaum
Es geht hier doch garnicht um extreme Sicherheit,
es geht auch nicht um Vergleiche "ähnlicher" Dateien, sondern nur um Ändernung jeweils einzelner Dateien mit sich selbst, womit die Möglichkeit extrem selten vorkommender Hash-Kollisionen sehr unwahrscheinlich ist, vor allem, wenn niemand (Hacker und Co.) absichtlich die Datei gezielt und mit enormem Aufwand daraufhin ändert. * das Archiv-Attribut * Datum des letzten Schreibzugriffs * und ansonsten sind doch immernoch MD5/SHA1/SHA256 ausreichend :roll: System.Hash : THashMD5 (128) THashSHA1 (128) THashSHA2 (224,256,384,512) THashBobJenkins (32, verwendet Dlelphi für Listen) |
AW: Optimaler Hash-Algorithmus und Strategie für Dateivergleiche, Verzeichnisbaum
Hab' einfach mal wieder die KI meiner Wahl befragt:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Eine Tabelle mit Dateinamen, Dateigröße, MD5-Hash und den Änderungsdaten sollten für die gestellte Aufgabe ausreichend sein. Das Erstellen einer Baumstruktur ist nicht erforderlich, es sei denn, man möchte es für die Anwender schön aussehen lassen. Dann reicht es aber aus die Baumstruktur nur für die Daten zu erstellen, die die Anwender auch zu Gesicht bekommen sollen und zwar erst dann, wenn die Daten zur Anzeige gebracht werden. Mein Vorgehen wäre: Rekursives Einlesen der Verzeichnisstruktur(en) und je Datei Pfad und Name, Dateigröße und Änderungsdaten in einer Tabelle speichern, dazu reichen FindFirst und FindNext mit TSearchRec aus. In der so erstellten Tabelle alle Sätze suchen, bei denen die Dateigröße mehr als einmal vorkommt:
SQL-Code:
Für die so ausgewählten Dateien den MD5-Hash berechnen und in der Tabelle speichern.
select * from tabelle a where exists (
select 1 from tabelle b where a.dateigroesse = b.dateigroesse group by b.dateigroesse having count(*) > 1 ) order by a.Dateigroesse; Anschließend die Dateien suchen lassen, bei denen die Kombination aus Dateigröße und MD5-Hash mehr als einmal vorkommen.
SQL-Code:
Aus dem Ergebnis wird dann die Anzeige für die Anwender befüllt, egal ob als TreeView, ListView oder auch einfach nur in 'nem DBGrid.
select * from tabelle a where exists (
select 1 from tabelle b where a.dateigroesse = b.dateigroesse and a.md5 = b.md5 group by b.dateigroesse, b.md5 having count(*) > 1 ) order by a.Dateigroesse, a.md5; |
AW: Optimaler Hash-Algorithmus und Strategie für Dateivergleiche, Verzeichnisbaum
Hallo Delphi.Narium,
ich bin nicht ganz sicher ob Du nur die Hash-Verwaltung beschreibst, oder ob Hash und Tree-Verwaltung zusammen integriert sind. Ich hatte ja schon geschrieben, dass dies vielleicht zwei verschiedene Aufgaben sind, die man nicht mischen sollte. Persönlich fände ich das Mischen beider Aufgaben aber sehr sinnvoll, eben weil es dann keine Redundanzen und Fehler durch getrente Datenhaltung auf das gleiche physikalische System geben kann. Ich meine Du beschreibst das aufnehmen der Hashes in die DB usw., und dann nur am Rande, dass daraus Tree, ListView usw. erstellt werden können. Welche Struktur schlägst Du denn dafür vor? Ich sehe dafür erstmal zwei sinnvolle Optionen in einer DB, vielleicht gibt es aber noch weitere:
Delphi-Quellcode:
In jedem Fall ist der "hash_value" sozusagen nur ein nützliches Abfallprodukt, mit dem sehr schnell verifiziert werden kann,
-- Adjacency List
CREATE TABLE FileSystem ( id INT PRIMARY KEY, parent_id INT, name VARCHAR(255), hash_value VARCHAR(255), type VARCHAR(50), FOREIGN KEY (parent_id) REFERENCES FileSystem(id) ); -- Nested Set CREATE TABLE FileSystem ( id INT PRIMARY KEY, name VARCHAR(255), lft INT, rgt INT, hash_value VARCHAR(255), type VARCHAR(50) ); ob es eine Datei bereits im System gibt und wie oft, unabhängig von der zu Grunde liegenden Baumstruktur. Das trifft das was ich suche schon ganz gut, danke sehr für die Idee mit der DB, das scheint eine Menge Vorzüge über einer spezifischen Klasse zu haben. Meine Frage war zu Deiner Erfahrung mit "Adjacency List" bzw. "Nested Set" oder eventuell auch "Flat table" mit kompletten Pfadangaben, was davon sich für das Durchlaufen von Filesystemen am besten eignet. Ich muss wahrscheinlich öfters die gesamten Filesysteme abgleichen, eben weil es mehrere Parteien gibt, welche unabhängig voneinander darauf zugreifen können. Remote-Verzeichniss (es kann mehrere geben) - ist ein zentrales Filesystem mit entfernten Daten, gehostet auf einem Fileserver im Internet - es kann durchaus mehrere, redundante oder auch ergänzende Remote-Verzeichnisse geben, auf verschiedenen Servern - dieses kann auf verschiedenen Wegen bearbeitet werden (z.B. automatisch aus einem anderem Dokumentenmanagementsystem, manuell per Website) - kann über verschiedene Protokolle bearbeitet werden (z.B. FTP, REST-API, direkt über WebClient auf dem Serversystem) - das von verschiedenen Parteien aus bearbeitet werden kann (automatische Ausleitungen von verschiedenen Systemen) - eine Verwaltung von Änderungen, Verzeichnisbaum auf dem Server ist erstmal nicht so ohne Weiteres möglich. - die Möglichkeit eine DB auf dem Server zu halten, welche das Ganze zentral abbildet, wäre denkbar ist aber auch eher ungewünscht. Lokal - ist eine lokale Kopie zur Zusammenführung, Bearbeitung und Analyse spezifischer Daten (lokal um den Server nicht zu belasten) - die lokale Kopie sollte möglichst nur bei Änderungen aktiv werden, daher die Frage nach Vergleich ganzer Baumstrukturen mit Hash - insbesondere das Einstellen neuer Dateien soll abgeprüft und verhindert werden (das gleiche File, mit anderem Namen, an andere Stelle). Eine Möglichkeit wäre noch das Erzeugen und Speichern von Fingerprints (*.md) auf den Servern, was aber auch eher ungewünscht ist. Die Datenmengen sind jedenfalls nicht so groß, dass eine Synchronisation Remote-Lokal generell ein Problem wäre. Deshalb trifft der Vergleich zu GIT/GitHub von Benmik schon ganz gut zu, nur eben geht es in erster Linie um binäre Dateien, nicht nur um Text. Es gäbe noch einen anderen Vergleich, z.B. mit einem FTP-Client, welcher auch Locale und Remote Filesysteme abgleichen kann, aber nicht unbedingt Änderungen in den Files erkennen kann. Ich denke der Ansatz mit einer DB ist einen Versuch Wert, das werde ich mal nächste Woche angehen. |
AW: Optimaler Hash-Algorithmus und Strategie für Dateivergleiche, Verzeichnisbaum
Habe momwentan keine Zeit ausführlich zu antworten, bitte hab' da etwas Geduld.
Ja, ich beschreibe nur die Hash-Verwaltung. Einen Baum halte ich für überflüssig. Mir erschließt sich nicht, wofür er nützlich sein sollte, außer für die Anzeige der Daten. Dann kann man ihn mit 'nem Treeview gezielt aus der benötigten Teilmenge der Tabelle erstellen. Die Baumstruktur für die Anzeige kann man immer "on the fly" aus den Pfadangeaben und dem Dateinamen erstellen und muss sie nicht permanet vorhalten, zumal sie für das Erkennen von Dubletten, ... keinerlei Mehrwert hat. |
AW: Optimaler Hash-Algorithmus und Strategie für Dateivergleiche, Verzeichnisbaum
Zitat:
Selbst wenn der Hash gleich groß wäre, wie die Datei, gäbe es theoretisch immernoch Kollisionen, da ja der Hash einen anderen Wert berechnet, wie die Ursprungsdaten und somit kann ebenfalls bei unterschiedlichen Dateien der Selbe hash entstehen. Was also bei den Hashs den Unterschied macht, ist wie groß er ist, je größer um so unwahrscheinlicher, und je besser die Berechnung ist, um unwahrscheinlicher. Billigster Hash, es werden einfach nur die Bytes in den Speicher geschoben, ohne Rückführung des Überlaufs, dann hat ein 32 Bit Hash schon ab einer Datei von 5 Byte Größe garantiert im ersten Byte alle Kollisionen drin, da immer nur die letzten 4 Byte zählen. |
AW: Optimaler Hash-Algorithmus und Strategie für Dateivergleiche, Verzeichnisbaum
Zitat:
Dazu wollte ich eine Struktur, die in der Lage ist das lokale und das remote Verzeichnis gleichermaßen abzubilden, um dies anzuzeigen und schnell abzugleichen und zu bearbeiten. Eigentlich sollte das schon sehr im Sinne eines FTP-Clients, oder TotalCommander oder ähnlich aussehen. Nur das meine App eben noch spezielle Operationen mit den Dateien machen muss, was ein FTP-Client nicht kann. Weil das aber eigentlich eine recht generelle Aufgabenstellung ist, so denke ich zumindest, wäre es doch sehr wahrscheinlich dass es dafür bereits ein existierendes, optimales Pattern oder vielleicht eine fertige Lösung gibt. Die Abbildung eines solchen Verzeichnissystems (Baum) zur synchronisierung ist doch prinzipiell auf allen OS, FAT/NTFS/APFS-Systemen und so weiter gleich, der einzige Unterschied sind Details wie Pfad-Delimiter, Zugriffsrechte, oder ähnlich. Das sollte man doch perfekt plattformübergreifend abbilden und verwalten können mit einer Klasse, zumindest meiner Meinung nach. |
AW: Optimaler Hash-Algorithmus und Strategie für Dateivergleiche, Verzeichnisbaum
Ok, ich fange an zu verstehen worauf Du hinaus möchtest und in meinem Kopf fängt ein Bild für die von Dir gewünschte Anwendung an zu entstehen. Aber wie ich das sinnvoll und anwenderfreundlich umsetzen sollte, keine Ahnung. Aber da ich heute schon ein paarmal diverse KIs "gequält" habe, dachte ich mir, versuchen wir es nochmal, aber ehrlich gesagt hab' ich keine Ahnung, ob das Ergebnis für Dich brauchbare Ideen oder umsetzbare Lösungsansätze enthält.
Die bisher von mir umgesetzen Dublettenprüfungen resultieren letztlich immer in einer Ja/Nein-Entscheidung, bei der dann nur noch entschieden werden muss, welche der Dubletten gelöscht werden soll. Deine Aufgabenstellung dürfte weit darüber hinausgehen. Der von der KI genannten Komponenten von JAM-Software scheinen nur für die VCL zur Verfügung zu stehen. Für mobile Apps also unbrauchbar. Unterhaltung mit der Ki meiner Wahl: Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Delphi-Quellcode:
procedure TForm1.FormCreate(Sender: TObject);
begin // Initialisieren Sie den TJamShellTree JamShellTree1.Root := 'C:\'; // Wählen Sie den Startordner aus // Ereignisse behandeln JamShellTree1.OnDblClick := JamShellTree1DblClick; JamShellTree1.OnShellChange := JamShellTree1ShellChange; JamShellTree1.OnGetImageIndex := JamShellTree1GetImageIndex; end; procedure TForm1.JamShellTree1DblClick(Sender: TObject); begin // Hier können Sie auf den Doppelklick auf einen Ordner oder eine Datei reagieren ShowMessage('Doppelklick auf: ' + JamShellTree1.SelectedPath); end; procedure TForm1.JamShellTree1ShellChange(Sender: TObject); begin // Hier können Sie auf Änderungen im aktuellen Ordner reagieren end; procedure TForm1.JamShellTree1GetImageIndex(Sender: TObject; Node: TTreeNode); begin // Hier können Sie den Bildindex für jeden Knoten festlegen // Beispiel: Node.ImageIndex := 0; // Ordnersymbol end; Zitat:
Zitat:
Zitat:
Delphi-Quellcode:
procedure TForm1.JamShellTree1GetImageIndex(Sender: TObject; Node: TTreeNode);
begin // Benutzerdefinierte Symbole festlegen if Node.Text = 'MeinOrdner' then Node.ImageIndex := 1 // Index des benutzerdefinierten Ordnersymbols else Node.ImageIndex := 0; // Standard-Ordnersymbol end; procedure TForm1.JamShellTree1ContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean); begin // Benutzerdefiniertes Kontextmenü hinzufügen if JamShellTree1.Selected <> nil then begin // Hier können Sie Ihr Kontextmenü erstellen und Aktionen hinzufügen // Beispiel: ShowMessage('Kontextmenü für ' + JamShellTree1.Selected.Text); end; end; Zitat:
Zitat:
Zitat:
Delphi-Quellcode:
procedure TForm1.FormCreate(Sender: TObject);
begin // Initialisieren Sie den TJamShellTree JamShellTree1.Root := 'C:\'; // Wählen Sie den Startordner aus // Ereignisse behandeln JamShellTree1.OnDblClick := JamShellTree1DblClick; JamShellTree1.OnShellChange := JamShellTree1ShellChange; JamShellTree1.OnGetImageIndex := JamShellTree1GetImageIndex; JamShellTree1.OnContextPopup := JamShellTree1ContextPopup; end; procedure TForm1.JamShellTree1DblClick(Sender: TObject); begin // Hier können Sie auf den Doppelklick auf einen Ordner oder eine Datei reagieren ShowMessage('Doppelklick auf: ' + JamShellTree1.SelectedPath); end; procedure TForm1.JamShellTree1ShellChange(Sender: TObject); begin // Hier können Sie auf Änderungen im aktuellen Ordner reagieren end; procedure TForm1.JamShellTree1GetImageIndex(Sender: TObject; Node: TTreeNode); begin // Benutzerdefinierte Symbole festlegen if Node.Text = 'MeinOrdner' then Node.ImageIndex := 1 // Index des benutzerdefinierten Ordnersymbols else Node.ImageIndex := 0; // Standard-Ordnersymbol end; procedure TForm1.JamShellTree1ContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean); begin // Benutzerdefiniertes Kontextmenü hinzufügen if JamShellTree1.Selected <> nil then begin // Hier können Sie Ihr Kontextmenü erstellen und Aktionen hinzufügen // Beispiel: ShowMessage('Kontextmenü für ' + JamShellTree1.Selected.Text); end; end; Zitat:
Zitat:
Zitat:
Delphi-Quellcode:
type
TComponent = class public procedure Add(Component: TComponent); virtual; abstract; procedure Remove(Component: TComponent); virtual; abstract; procedure Display(depth: integer); virtual; abstract; end; TComposite = class(TComponent) private children: TList; name: string; public constructor Create(n: string); procedure Add(Component: TComponent); override; procedure Remove(Component: TComponent); override; procedure Display(depth: integer); override; end; TLeaf = class(TComponent) private name: string; public constructor Create(n: string); procedure Add(Component: TComponent); override; procedure Remove(Component: TComponent); override; procedure Display(depth: integer); override; end; Zitat:
Zitat:
Zitat:
Delphi-Quellcode:
type
TComponent = class public procedure Add(Component: TComponent); virtual; abstract; procedure Remove(Component: TComponent); virtual; abstract; procedure Display(depth: integer); virtual; abstract; end; TComposite = class(TComponent) private children: TList; name: string; public constructor Create(n: string); procedure Add(Component: TComponent); override; procedure Remove(Component: TComponent); override; procedure Display(depth: integer); override; end; TLeaf = class(TComponent) private name: string; public constructor Create(n: string); procedure Add(Component: TComponent); override; procedure Remove(Component: TComponent); override; procedure Display(depth: integer); override; end; implementation constructor TComposite.Create(n: string); begin name := n; children := TList.Create; end; procedure TComposite.Add(Component: TComponent); begin children.Add(Component); end; procedure TComposite.Remove(Component: TComponent); begin children.Remove(Component); end; procedure TComposite.Display(depth: integer); var i: integer; begin writeln(StringOfChar('-', depth) + name); for i := 0 to children.Count - 1 do TComponent(children[i]).Display(depth + 2); end; constructor TLeaf.Create(n: string); begin name := n; end; procedure TLeaf.Add(Component: TComponent); begin writeln('Cannot add to a leaf'); end; procedure TLeaf.Remove(Component: TComponent); begin writeln('Cannot remove from a leaf'); end; procedure TLeaf.Display(depth: integer); begin writeln(StringOfChar('-', depth) + name); end; Zitat:
Zitat:
Zitat:
|
AW: Optimaler Hash-Algorithmus und Strategie für Dateivergleiche, Verzeichnisbaum
Danke sehr für die Mühe.
Ich habe mal ein bischen darüber nachgedacht wellche Pattern in Frage kämen und habe hier ein grobes Konzept, was hoffentlich das was ich möchte gut abbildet. Die spezifischen Adapter erzeugen die Hashes systemabhängig und geben diese an den zentralen Controller weiter.
Delphi-Quellcode:
Alle Funktionalität um die verschiedenen Lokationen abzubilden, synchron zu halten bzw. separat zu bearbeiten wäre zentral in dem PathController gekapselt.
program PathManagement;
{$APPTYPE CONSOLE} uses System.SysUtils, System.Generics.Collections; type IPathInterface = interface function GetFormattedPath: string; end; TPathController = class private FAdapters: TList<IPathInterface>; public constructor Create; destructor Destroy; override; procedure Attach(Adapter: IPathInterface); procedure NotifyAdapters; end; TPathAdapter = class(TInterfacedObject, IPathInterface) protected FController: TPathController; FPath: string; // Jeder Adapter verwaltet nun seinen eigenen Pfad public constructor Create(Controller: TPathController; const Path: string); function GetFormattedPath: string; virtual; abstract; end; TPathAdapter_Windows = class(TPathAdapter) public function GetFormattedPath: string; override; end; TPathAdapter_Unix = class(TPathAdapter) public function GetFormattedPath: string; override; end; { TPathController Implementation } constructor TPathController.Create; begin inherited Create; FAdapters := TList<IPathInterface>.Create; end; destructor TPathController.Destroy; begin FAdapters.Free; inherited Destroy; end; procedure TPathController.Attach(Adapter: IPathInterface); begin FAdapters.Add(Adapter); end; procedure TPathController.NotifyAdapters; var Adapter: IPathInterface; begin for Adapter in FAdapters do WriteLn(Adapter.GetFormattedPath); end; { TPathAdapter Implementation } constructor TPathAdapter.Create(Controller: TPathController; const Path: string); begin inherited Create; FController := Controller; FPath := Path; FController.Attach(Self); end; { TPathAdapter_Windows Implementation } function TPathAdapter_Windows.GetFormattedPath: string; begin //! Nur als Beispiel womrum es geht, genau solche unnötigen Umkopierungen möchte ich durch Baum-Auflösung im PathController Vermeiden Result := StringReplace(FPath, '/', '\', [rfReplaceAll]); end; { TPathAdapter_Unix Implementation } function TPathAdapter_Unix.GetFormattedPath: string; begin //! Nur als Beispiel womrum es geht, genau solche unnötigen Umkopierungen möchte ich durch Baum-Auflösung im PathController Vermeiden Result := StringReplace(FPath, '\', '/', [rfReplaceAll]); end; var Controller: TPathController; WinAdapter, UnixAdapter: IPathInterface; begin try Controller := TPathController.Create; WinAdapter := TPathAdapter_Windows.Create(Controller, 'C:\Users\Example\LocalDocuments'); // hier hinter sollten sich die identischen Dokumente befinden UnixAdapter := TPathAdapter_Unix.Create(Controller, 'FTP://mytest.com/remote/Documents'); // Controller.NotifyAdapters; // Kann verschiedene Lokations-übergreifende Aktionen anstossen ReadLn; finally Controller.Free; end; end. Alle Zugriffe, aus Sicht einer einzelnen Lokation wären in den PathAdaptern und deren PathInterface gekapselt. Damit komme ich glaube ich weiter. Ich werde zuerst mal versuchen in dem PathControll mit einem TFdMemTable zu arbeiten, wenn das nicht reicht, dann kann ich immer noch auf Bäume und sonstiges umsteigen. Vielen Dank erstmal an alle. |
AW: Optimaler Hash-Algorithmus und Strategie für Dateivergleiche, Verzeichnisbaum
Zitat:
Ist seit 2.0 die Standard Hashfunktion für Dictionary und Co |
Alle Zeitangaben in WEZ +1. Es ist jetzt 05: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 by Thomas Breitkreuz