Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Datenbanken (https://www.delphipraxis.net/15-datenbanken/)
-   -   Delphi Speichern eines großen Arrays (https://www.delphipraxis.net/149837-speichern-eines-grossen-arrays.html)

Shubit 31. Mär 2010 16:21


Speichern eines großen Arrays
 
Liste der Anhänge anzeigen (Anzahl: 1)
Ich versuche derzeit meinen Vokabeltrainer etwas zu optimieren. Dabei habe ich es besonders auf die Speicherung der Vokabeln abgesehen:
Delphi-Quellcode:
  TVocabulary = record
    Language1: String[100];
    Language2: String[100];
    Note: String[100];
    AddedOn: TDateTime;
    LastTraining: TDateTime;
    Chance: Single;
    Mistakes: Integer;
    Trials: Integer;
    Box: Integer;
  end;
Während das Programm läuft liegen alle Vokabeln in einem array of TVocabulary vor und werden beim schließen in einer File of TVocabulary gespeichert.
Das hat aus meiner Sicht folgende Vorteile:
1. Mit dem Array lässt es sich sehr gut arbeiten (man brauch kein seak, read oder write)
2. Durch das Anhängen eines zusätzlichen Datensatzes lassen sich leicht ein paar Einstellungen zur Vokabelliste speichern, sodass jede Datei eine eigenständige Vokabelliste darstellt, die man problemlos verschieben oder an Freunde schicken kann (mit den Einstellungen).
3. Da alle Daten zur Laufzeit im Arbeitsspeicher liegen, gehen auch aufwändigere Aktionen (statistische Daten) sehr schnell.
4. Keine Datenbankinstallation oder gar Datenbanktreiber notwendig. Die File of TVocabulary wird von einer selbst geschriebenen Klasse (hab sie auch mal angehangen) verwaltet

aber auch ein paar Nachteile:
1. Bei vielen Vokabeln (~100k) dauert das Laden in den Array und das Speichern in die Datei etwas länger (von der Darstellung in einer Listbox ganz zu schweigen^^)
2. Die File of TVocabulary benötigt eine Begrenzung (Finalization) für die Strings. Dies bedeutet das man sich endscheiden muss zwischen einer möglichst unbegrenzten Stringlänge (die 100 Zeichen ist schon unterste Schmerzgrenze) und keiner überflüssigen Arbeitsspeicherverschwendung, denn egal wie lang die Vokabel dann wirklich ist, wird Platz für 300 Zeichen reserviert.


Was ich nun suche ist eigentlich einen Weg, der die gleichen Vorteile bietet, aber auch die Nachteile deutlich minimiert. Und bevor ich wild drauf los programmiere, wollt ich einfach mal euren Rat hören bzw. eure Ideen hören.
Meine Idee wäre erstmal:
a) Die Klasse zur Verwaltung so anpassen, dass sie nur die aktuell ausgewählte Vokabel lädt und in der angesprochenden Listbox immer nur so 1000 Vokabeln dargestellt werden (muss man sich dann etwas Mühe mit dem Seak bei der File of TVocabulary damit man nicht verrutscht). Damit auch die Statistiken schnell dargestellt werden, könnte man dafür dann ein paar Variablen mitführen (z.B. immer den Index der Vokabel mit den meisten Fehlern in einem Integer speichern). Wär halt etwas Aufwand...
b) Datenbank, aber davon hab ich eigentlich keine Ahnung und ich möchte dem Benutzer keine Datenbankinstallation zumuten.

So, jetzt seid ihr dran :)

s.h.a.r.k 31. Mär 2010 16:26

Re: Speichern eines großen Arrays
 
Datenbanken wäre für so etwas sehr gut geeignet und kommen auch mit sehr großen Datenmengen klar. Wenn du nichts installieren willst, so brauchst du nur eine Embedded-Datenbank nutzen, SQLite wäre hier ein Beispiel.

Die Datensätze dann in einem Array zu halten, finde ich etwas unhandlich. Da gefallen mit TList (bzw. TObjectList) wesentlich besser, wobei ich aus dem Record eine Klasse gemacht hätte.

Shubit 31. Mär 2010 17:09

Re: Speichern eines großen Arrays
 
Wenn ich eine Datenbank habe, brauch ich ja keinen Array mehr. Dann hab ich noch 2 Fragen:
a) Also ich möchte das der Nutzer nur eine Datei pro Vokabelliste hat und keine zusätzlichen dll's. Mit welchen Komponenten/Datenbank geht das am besten?
b) Wo finde ich ein gutes Tutorial für SQLite in Delphi (2010)?
c) Wie wähle ich dann aus der Tabelle einen zufälligen Eintrag aus? (ich kann ja keine zufällige ID wählen, weil diese ja nicht existieren muss)

s.h.a.r.k 31. Mär 2010 19:30

Re: Speichern eines großen Arrays
 
Hier im Forum findest du eigentlich sehr viel über SQLite, ansonsten solltest du dir mal die Abfrage-Sprache SQL anschauen. Schwer zu verstehen ist diese eigentlich nicht. Findest hier im Forum Hilfen zu SQL-Problemen, aber wohl kein Tutorial. Hier findest ein paar, die teilweise auch auf SQLite anzuwenden sind (ZEOS).

Ich glaube nicht, dass man via SQL einen zufälligen Datensazu auswählen kann, aber hierzu eine Idee: Du selektierst alle IDs dazu (z.B.: SELECT UserID FROM Users) und liest anschließend die Anzahl der zurückgegeben Datensätze aus. Somit hast du die Obergrenze für die Zufallszahl, die du via Delphi generieren lassen kannst. Über diese Zahl selektierst du dann den Datensatz, was du über die IDs, die du ausgelesen hast, bewerkstelligen kannst.

// edit

zu a) wenn du eine Embedded-Datenbank nutzt, dann musst du immer eine DLL liefern inkl. der Datenbank-Datei(en) in der die Daten stehen. Das ist zumindest mein Stand der Dinge.

Shubit 1. Apr 2010 09:00

Re: Speichern eines großen Arrays
 
Na gut, über den zufälligen Datensatz mach ich mir dann nochmal Gedanken wenn der Rest läuft (wird schon irgendiwe gehen).

Ka ob es eine gute Endscheidung war, aber ich hab mich jetzt erstmal für die Zeos Komponenten und Sqlite endschieden.
Der Sql Syntax ist ja an ausreichend Stellen ganz gut erklärt (auch wenn die Anfürhungszeichen überall etwas anderes gehandhabt werden). Aber in Verbindung mit Delphi hapert es jetzt:

Delphi-Quellcode:
  ZConnection := TZConnection.Create(self);
  ZQuery := TZQuery.Create(self);
  ZTable := TZTable.Create(self);

  with ZConnection do
  begin
    Protocol := 'sqlite-3';
    Database := 'database.db';
    Connect;
  end;

  ZQuery.Connection := ZConnection;
  ZTable.Connection := ZConnection;

  with ZQuery do
  begin
    SQL.Clear;
    SQL.Add('CREATE TABLE IF NOT EXISTS VocabularyList (');
    SQL.Add('id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,');
    SQL.Add('Language1 VARCHAR(255) NULL,');
    SQL.Add('Article1 VARCHAR(7) NULL,');
    SQL.Add('Language2 VARCHAR(255) NULL,');
    SQL.Add('Article2 VARCHAR(7) NULL,');
    SQL.Add('Note VARCHAR(255) NULL,');
    SQL.Add('Chance DOUBLE NULL,');
    SQL.Add('AddedOn DOUBLE NULL,');
    SQL.Add('LastTraining DOUBLE NULL,');
    SQL.Add('Mistakes INTEGER NULL,');
    SQL.Add('Trials INTEGER NULL,');
    SQL.Add('Box INTEGER NULL');
    SQL.Add(');');

    ExecSQL;
  end;

  with ZQuery do
  begin
    SQL.Clear;
    SQL.Add('INSERT INTO VocabularyList (Language1, Article1, Language2, ' +
        'Article2, Note, Chance, AddedOn, LastTraining, Mistakes, Trials, Box) '
        + 'VALUES (:Language1, :Article1, :Language2, :Article2, :Note, ' +
        ':Chance, :AddedOn, :LastTraining, :Mistakes, :Trials, :Box)');
    ParamByName('Language1').AsString := 'Wort';
    ParamByName('Article1').AsString := 'das';
    ParamByName('Language2').AsString := 'word';
    ParamByName('Article2').AsString := 'the';
    ParamByName('Chance').AsFloat := 0.5;
    ParamByName('AddedOn').AsDateTime := Now;
    ParamByName('LastTraining').AsDateTime := Now;
    ParamByName('Mistakes').AsInteger := 0;
    ParamByName('Trials').AsInteger := 0;
    ParamByName('Box').AsInteger := 0;

    ExecSQL;
  end;

  with ZQuery do
  begin
    SQL.Clear;
    ZQuery.SQL.Add('SELECT * FROM VocabularyList');
  end;
Also das Erstellen der Tablle funktioniert noch, das Einfügen eines Datensatz scheitert an einen Komma-Syntax Fehler und die Abfrage zum Schluss sollte zwar funktionieren, dann hab wie greif ich dann auf die Abgefragten Daten zu (für die Ausgabe)?

s.h.a.r.k 1. Apr 2010 10:03

Re: Speichern eines großen Arrays
 
Beim Insert hast du ParamByName('Note') vergessen.

Und zum Select nutzt du kein ExecSQL, sondern ein Open und Close :zwinker:

PS: Du brauchst kein Clear und Add zu verwenden, wenn du sofort alles in Query.SQL.Text wirfst. (zumindest klappt das bei AnyDAC so, müsste bei ZEOS aber auch gehen)

Shubit 1. Apr 2010 10:16

Re: Speichern eines großen Arrays
 
Ok danke.

Hmm das mit dem Open hab ich schon gelesen, allerdings war das nicht so meine Frage (wobei die vielleicht schlecht gestellt war).
Als ich die File of TVocabulary hatte, wusste ich wie ich die in den array schreibe und dort dann auf die einzelnen Elemente und Eigenschaften zugreife und nun? Wie komm ich an die Daten ran, die ich die Datenbank gerade so sorgfältigt habe auslesen lassen?
ZQuery.SQL.Add('SELECT * FROM VocabularyList');
ZQuery.Open;
Wie kann ich die einzelnen Zeilen jetzt durchgehen und mir anschauen was in den Spalten drinsteht? Und wie nutzte ich den UPDATE Befehl zum Zurückschreiben von Änderungen?
Vielleicht steh ich auch gerade auch nur mächtig auf den Schlauch und ein kurzes Beispiel würde schon reichen.

Teekeks 1. Apr 2010 10:21

Re: Speichern eines großen Arrays
 
Zuerst mal ein First.
Dann mit ParambyName('Note').asstring.
usw.
Den nächsten Datensatz bekommst du mit Next;

DeddyH 1. Apr 2010 10:30

Re: Speichern eines großen Arrays
 
Auslesen:
Delphi-Quellcode:
DeineQuery.Close;
DeineQuery.SQL.Text := 'SELECT Feld1, Feld2 FROM Tabelle';
DeineQuery.Open;
while not DeineQuery.EOF do
  begin
    ShowMessage(DeineQuery.FieldByName('Feld1').AsString);
    ShowMessage(DeineQuery.FieldByName('Feld2').AsString);
    DeineQuery.Next;
  end;
Einfügen:
Delphi-Quellcode:
DeineQuery.Close;
DeineQuery.SQL.Text := 'INSERT INTO Tabelle(Feld1, Feld2) VALUES(:feld1,:feld2)';
DeineQuery.ParamByName('feld1').Value := wert1;
DeineQuery.ParamByName('feld2').Value := wert2;
DeineQuery.ExecSQL;
Aktualisieren:
Delphi-Quellcode:
DeineQuery.Close;
DeineQuery.SQL.Text := 'UPDATE Tabelle SET Feld1=:feld1, Feld2=:feld2 WHERE ID=:id';
DeineQuery.ParamByName('feld1').Value := wert1;
DeineQuery.ParamByName('feld2').Value := wert2;
DeineQuery.ParamByName('id').Value := PK_des_Datensatzes;
DeineQuery.ExecSQL;

Shubit 1. Apr 2010 11:03

Re: Speichern eines großen Arrays
 
@Teekeks: etwas sehr kurzes Beispiel, da ist mir das von DeddyH doch lieber :D

Zu dem Syntax Fehler:
Das geht:
Delphi-Quellcode:
    SQL.Add('INSERT INTO VocabularyList (Language1, Article1, Language2, ' +
        'Article2, Note, Chance, AddedOn, LastTraining, Mistakes, Trials, Box) '
        + 'VALUES (:Language1,:Article1,:Language2,:Article2,:Note,' +
        ':Chance,:AddedOn,:LastTraining,:Mistakes,:Trials,:Box);');
    ParamByName('Language1').Value := 'Wort';
    ParamByName('Article1').Value := 'das';
    ParamByName('Language2').Value := 'word';
    ParamByName('Article2').Value := 'the';
    ParamByName('Note').Value := 'Notiz';
    ParamByName('Chance').AsFloat := 0.5;
    ParamByName('AddedOn').AsDateTime := Now;
    ParamByName('LastTraining').AsDateTime := Now;
    ParamByName('Mistakes').AsInteger := 0;
    ParamByName('Trials').AsInteger := 0;
    ParamByName('Box').AsInteger := 0;
    ExecSQL;
Das geht nicht:
Delphi-Quellcode:
    SQL.Add('INSERT INTO VocabularyList (Language1, Article1, Language2, ' +
        'Article2, Note, Chance, AddedOn, LastTraining, Mistakes, Trials, Box) '
        + 'VALUES (:Language1,:Article1,:Language2,:Article2,:Note,' +
        ':Chance,:AddedOn,:LastTraining,:Mistakes,:Trials,:Box);');
    ParamByName('Language1').AsString := 'Wort';
    ParamByName('Article1').AsString := 'das';
    ParamByName('Language2').AsString := 'word';
    ParamByName('Article2').AsString := 'the';
    ParamByName('Note').AsString := 'Notiz';
    ParamByName('Chance').AsFloat := 0.5;
    ParamByName('AddedOn').AsDateTime := Now;
    ParamByName('LastTraining').AsDateTime := Now;
    ParamByName('Mistakes').AsInteger := 0;
    ParamByName('Trials').AsInteger := 0;
    ParamByName('Box').AsInteger := 0;
    ExecSQL;
Scheinbar funktioniert die AsString procedure nicht, weshalb die Datenbank dann meckert das da 5 Kommas ohne Strings dazwischen sind.



Zufälliger Eintrag:
a) Array mit allen gültigen ID's erstellen und über zufälligen Index des Arrays ...
b) Alle Zeilen aus der Datenbank holen und dann eine zufällige Anzahl an Schritten weiter gehen. gibt es sowas wie ZQuery.Count (Anzahl der Zeilen)?
c) Ich denke das macht man zwar eher nicht, aber man könnte ja auch beim Löschen eines Eintrages alle größeren ID's anpassen und dann hätte man direkt eine zufällige ID nehmen, weil man wüsste das alle ID's von 1 bis zur Anzahl Zeilen vergeben sind.

Ich brauch den zufälligen Eintrag für das Vokabeltraining, derzeit wird das ganze so ermittelt:
Delphi-Quellcode:
        repeat
          mNeuerIndex := random(Length(mVokabelListe));
          x := random;
        until (x < mVokabelListe[mNeuerIndex].Chance) and
          (mNeuerIndex <> mLetzterIndex);
(die Chance gibt also die Wahrscheinlichkeit an mit der die Vokabel ausgewählt wird)
Cool wär halt: 'SELECT * FROM VokabelListe WHERE Chance > :Zufallszahl AMD id <=> :LetzteID AMD id = .ZufaelligeID'

DeddyH 1. Apr 2010 11:13

Re: Speichern eines großen Arrays
 
Es gibt die Property RecordCount, die soll aber nicht immer zuverlässig sein (hab ich zumindest gelesen, ich nutze die eigentlich nie). Was auf jeden Fall zuverlässige Werte liefern sollte, ist ein
SQL-Code:
SELECT COUNT(ID) FROM Tabelle
Damit bekommst Du aber lediglich die Anzahl der Datensätze der Tabelle, nicht hingegen deren ID an sich. Also würde ich die Anzahl einmalig ermitteln und mir in einer Variablen merken (zumindest solange ausgeschlossen ist, dass während des Programmablaufs Datensätze eingefügt oder gelöscht werden können). Einen zufälligen DS müsste man dann so bekommen können (ungetestet):
Delphi-Quellcode:
Query.First;
for i := 1 to Random(Anzahl_DS) do
  Query.Next;

s.h.a.r.k 1. Apr 2010 12:19

Re: Speichern eines großen Arrays
 
Zitat:

Zitat von DeddyH
Es gibt die Property RecordCount, die soll aber nicht immer zuverlässig sein (hab ich zumindest gelesen, ich nutze die eigentlich nie).

:shock: uiuiui... sicher?

Blup 1. Apr 2010 12:34

Re: Speichern eines großen Arrays
 
Ich vermute das mit einer Datenbank die genannten Probleme eher noch wachsen und neue hinzukommen werden, insbesondere wenn man sich noch nie mit Datenbanken beschäftigt hat.

Wenn man Strings variabler Größe verwendet, muss man zwar auf "File of" verzichten und die Daten z.B. Binär speichern, aber da die meisten Begriffe kürzer sind, wird die Datei auch kleiner. Das Problem mit der Ladezeit lässt sich dann vermutlich einfach dadurch lösen, daß man die Daten komprimiert speichert/liest und nur im Speicher packt/entpackt. Das lässt sich nach meiner Meinung relativ simpel und schnell realisieren.

Shubit 1. Apr 2010 13:38

Re: Speichern eines großen Arrays
 
Nun ich bin für alle Lösungen offen, aber da Datenbanken extra für großen Mengen an Daten ausgelegt sind, sollten sie gerade für längere Vokabellisten sehr gut geeignet sein und wenn sie die restlichen Anforderungen erfüllen, lohnt es sich sicher sich mal damit zu beschäftigen.

Mir ist gerade noch eine Methode zur Ermittlung eines eines zufälligen Datensatzes eingefallen:
Delphi-Quellcode:
DeineQuery.SQL.Text := 'SELECT COUNT(ID) FROM Tabelle';
DeineQuery.Open;
x := DeineQuery //ja hier müsste irgendwie die Antwort der SQL hin
x := x-2;
repeat
DeineQuery.SQL.Text := 'SELECT * FROM VokabelListe WHERE id != LetzteID LIMIT :x, 1 ';
DeineQuery.ParamByName('x').Value := random(x);
DeineQuery.Open;
until DeineQuery.FieldByName('Feld1').AsFloat > Random;

DeddyH 1. Apr 2010 14:03

Re: Speichern eines großen Arrays
 
Moment, Du möchtest also auf jeden Fall vermeiden, dass einzelne Vokabeln doppelt abgefragt werden? Dann könntest Du Dir die bereits abgefragten DS (besser gesagt: deren IDs) in einer Liste merken und das SQL entsprechend dynamisch zusammenbauen. Angenommen, die IDs 3 und 7 wurden bereits abgefragt, dann könnte das nächste Statement dann so aussehen:
SQL-Code:
SELECT * FROM VokabelListe
WHERE ID NOT IN (3,7)
Allerdings kenne ich mich mit der Syntax von SQLite nicht aus, evtl. muss das dort anders formuliert werden.

Achja, und zu Deinem Problemchen: enweder einen Alias verwenden
Delphi-Quellcode:
DeineQuery.SQL.Text := 'SELECT COUNT(ID) AS Anzahl FROM Tabelle';
DeineQuery.Open;
x := DeineQuery.FieldByName('Anzahl').Value;
oder über den Index auf das Feld zugreifen
Delphi-Quellcode:
DeineQuery.SQL.Text := 'SELECT COUNT(ID) FROM Tabelle';
DeineQuery.Open;
x := DeineQuery.Fields[0].Value;
Ich persönlich würde den Alias verwenden, das ist auch später noch nachzuvollziehen.

Shubit 1. Apr 2010 17:17

Re: Speichern eines großen Arrays
 
Eigentlich wollt ich nur vermeiden dass er die gerade gefragt Vokabel gleich wieder fragt, aber ansonsten gute Idee ;)

Shubit 1. Apr 2010 19:55

Re: Speichern eines großen Arrays
 
Was muss ich jetzt alles machen wenn ich die Tabelle in einem Grid darstellen will?

DeddyH 1. Apr 2010 20:01

Re: Speichern eines großen Arrays
 
In einem DBGrid? Eine Datasource dazupacken, diese dem DBGrid und als Dataset die Query zuweisen.

xZise 1. Apr 2010 20:03

Re: Speichern eines großen Arrays
 
Moin,
erstmal mach wegen deiner Gridfrage einen neuen Post auf. Und bezüglich SQLite könntest du eventuell das Problem haben, wenn du eineindeutige IDs benutzt. Wenn wir also keine ID jemals zweimal vergeben und zu Anfang mal n Vokabeln haben, dann haben wir 1 bis n IDs (wenn ich mich richtig erinnere beginnen die IDs bei 1). Wenn du jetzt eine ID x zwischen n und 1 löscht und einen Eintrag hinzufügst, dann hat er die ID n + 1 und nicht x.

Ich würde also mir alle IDs holen (SELECT ID FROM Vocs) und dann zufällig eine davon Auswählen.

MfG
Fabian

DP-Maintenance 1. Apr 2010 23:50

DP-Maintenance
 
Dieses Thema wurde von "Luckie" von "Sonstige Fragen zu Delphi" nach "Datenbanken" verschoben.
Mittlerweile sind wir wohl bei Datenbanken angekommen. Für das Internetupdate deines Programms machst du aber bitte einen neuen Thread auf. ;)

Blup 6. Apr 2010 14:26

Re: Speichern eines großen Arrays
 
Zitat:

Zitat von Shubit
Nun ich bin für alle Lösungen offen, aber da Datenbanken extra für großen Mengen an Daten ausgelegt sind, sollten sie gerade für längere Vokabellisten sehr gut geeignet sein und wenn sie die restlichen Anforderungen erfüllen, lohnt es sich sicher sich mal damit zu beschäftigen.

Die von dir angeführten Probleme scheinen aber eher mit der Struktur der Anwendung oder der Daten zusammen zu hängen. Wenn alle Daten aus einer Tabelle der Datenbank abgefragt werden, geht das nicht schneller als das Lesen einer Datei. Eine Datenbankanwendung solltest du vieleicht von Grund auf neu entwickeln.

Hier noch Beispielcode zu meinem Lösungsvorschlag mit einer komprimierten Datei:
Delphi-Quellcode:
uses
  Classes, Contnrs;

type
  TVocabulary = class
    Language1: string;
    Language2: string;
    Note: string;
    AddedOn: TDateTime;
    LastTraining: TDateTime;
    Chance: Single;
    Mistakes: Integer;
    Trials: Integer;
    Box: Integer;
  protected
    procedure WriteToStream(AStream: TStream);
    procedure ReadFromStream(AStream: TStream);
  end;

  TVocabularyList = class(TObjectList)
    LanguageName1: string;
    LanguageName2: string;
    Note: string;
  public
    procedure WriteToStream(AStream: TStream);
    procedure ReadFromStream(AStream: TStream);
  end;

  procedure SaveToFile(AList: TVocabularyList; AFilename: string);
  procedure LoadFromFile(AList: TVocabularyList; AFilename: string);

implementation

uses
  SysUtils, Windows, ZLib;

type
  TFileHeader = record
    Ident: array[1..10] of Char;
    procedure WriteToStream(AStream: TStream);
    procedure ReadFromStream(AStream: TStream);
    class operator Explicit(v: TFileHeader): string;
  end;

  TRecordHeader = record
    Ident: string;
    Version: LongWord;
    procedure WriteToStream(AStream: TStream);
    procedure ReadFromStream(AStream: TStream);
  end;

const
  VocabularyIdent_File:  TFileHeader  = (Ident: ('V','o','c','a','b','u','l','a','r','y'));
  VocabularyIdent_Header: TRecordHeader = (Ident: 'Header'; Version: $00010000);
  VocabularyIdent_Items: TRecordHeader = (Ident: 'Items'; Version: $00010000);

procedure WriteStringToStream(AStream: TStream; const AValue: string);
var
  c: LongWord;
begin
  c := Length(AValue);
  AStream.WriteBuffer(c, SizeOf(c));
  AStream.WriteBuffer(PChar(AValue)^, c);
end;

procedure ReadStringFromStream(AStream: TStream; var AValue: string);
var
  c: LongWord;
begin
  AStream.ReadBuffer(c, SizeOf(c));
  SetLength(AValue, c);
  AStream.ReadBuffer(PChar(AValue)^, c);
end;

procedure TFileHeader.WriteToStream(AStream: TStream);
begin
  AStream.WriteBuffer(Ident, SizeOf(Ident));
end;

class operator TFileHeader.Explicit(v: TFileHeader): string;
var
  i: Integer;
begin
  Result := '';
  for i := Low(v.Ident) to High(v.Ident) do
  begin
    if v.Ident[i] = #0 then
      Exit;

    Result := Result + v.Ident[i];
  end;
end;

procedure TFileHeader.ReadFromStream(AStream: TStream);
begin
  AStream.ReadBuffer(Ident, SizeOf(Ident));
end;

procedure TRecordHeader.WriteToStream(AStream: TStream);
begin
  WriteStringToStream(AStream, Ident);
  AStream.WriteBuffer(Version, SizeOf(Version));
end;

procedure TRecordHeader.ReadFromStream(AStream: TStream);
begin
  ReadStringFromStream(AStream, Ident);
  AStream.ReadBuffer(Version, SizeOf(Version));
end;

procedure TVocabularyList.WriteToStream(AStream: TStream);
var
  i, c: LongWord;
begin
  {Datei-Inhalt}
  VocabularyIdent_File.WriteToStream(AStream);
  {Header}
  VocabularyIdent_Header.WriteToStream(AStream);
  WriteStringToStream(AStream, LanguageName1);
  WriteStringToStream(AStream, LanguageName2);
  WriteStringToStream(AStream, Note);
  {Items}
  VocabularyIdent_Items.WriteToStream(AStream);
  c := Count;
  AStream.WriteBuffer(c, SizeOf(c));

  for i := 0 to c - 1 do
    TVocabulary(Items[i]).WriteToStream(AStream);
end;

procedure TVocabularyList.ReadFromStream(AStream: TStream);
{---}
  procedure CheckRecordHeader(const AValue, AReferenz: TRecordHeader);
  begin
    if AValue.Ident <> AReferenz.Ident then
      raise Exception.CreateFmt('ungültiges Dateiformat, ''%s'' erwartet',
        [AReferenz.Ident]);
  end;
{---}
  procedure CheckRecordVersion(const AValue, AReferenz: TRecordHeader);
  begin
    if AValue.Ident <> AReferenz.Ident then
      raise Exception.CreateFmt('ungültige Recordversion ''%s'' V%d.%d',
        [AValue.Ident, HiWord(AValue.Version), LoWord(AValue.Version)]);
  end;
{---}
var
  i, c: LongWord;
  FileHeader: TFileHeader;
  RecordHeader: TRecordHeader;
  v: TVocabulary;
begin
  LanguageName1 := '';
  LanguageName2 := '';
  Note         := '';
  Clear;
  {Datei-Inhalt}
  FileHeader.ReadFromStream(AStream);
  if FileHeader.Ident <> VocabularyIdent_File.Ident then
    raise Exception.CreateFmt('ungültiges Dateiformat, %s erwartet',
      [string(VocabularyIdent_File)]);
  {Header}
  RecordHeader.ReadFromStream(AStream);
  CheckRecordHeader(RecordHeader, VocabularyIdent_Header);
  CheckRecordVersion(RecordHeader, VocabularyIdent_Header);
  ReadStringFromStream(AStream, LanguageName1);
  ReadStringFromStream(AStream, LanguageName2);
  ReadStringFromStream(AStream, Note);
  {Items}
  RecordHeader.ReadFromStream(AStream);
  CheckRecordHeader(RecordHeader, VocabularyIdent_Items);
  CheckRecordVersion(RecordHeader, VocabularyIdent_Items);
  AStream.ReadBuffer(c, SizeOf(c));
  for i := 0 to c - 1 do
  begin
    v := TVocabulary.Create;
    try
      v.ReadFromStream(AStream);
    except
      v.Free;
      raise;
    end;
    Add(v);
  end;
end;

procedure TVocabulary.WriteToStream(AStream: TStream);
begin
  WriteStringToStream(AStream, Language1);
  WriteStringToStream(AStream, Language2);
  WriteStringToStream(AStream, Note);
  AStream.WriteBuffer(AddedOn,     SizeOf(AddedOn));
  AStream.WriteBuffer(LastTraining, SizeOf(LastTraining));
  AStream.WriteBuffer(Chance,      SizeOf(Chance));
  AStream.WriteBuffer(Mistakes,    SizeOf(Mistakes));
  AStream.WriteBuffer(Trials,      SizeOf(Trials));
  AStream.WriteBuffer(Box,         SizeOf(Box));
end;

procedure TVocabulary.ReadFromStream(AStream: TStream);
begin
  ReadStringFromStream(AStream, Language1);
  ReadStringFromStream(AStream, Language2);
  ReadStringFromStream(AStream, Note);
  AStream.ReadBuffer(AddedOn,     SizeOf(AddedOn));
  AStream.ReadBuffer(LastTraining, SizeOf(LastTraining));
  AStream.ReadBuffer(Chance,      SizeOf(Chance));
  AStream.ReadBuffer(Mistakes,    SizeOf(Mistakes));
  AStream.ReadBuffer(Trials,      SizeOf(Trials));
  AStream.ReadBuffer(Box,         SizeOf(Box));
end;

procedure SaveToFile(AList: TVocabularyList; AFilename: string);
var
  stream: TStream;
begin
  stream := TFileStream.Create(AFilename, fmCreate);
  try
    stream := TCompressionStream.Create(clDefault, stream);
    AList.WriteToStream(stream);
  finally
    stream.Free;
  end;
end;

procedure LoadFromFile(AList: TVocabularyList; AFilename: string);
var
  stream: TStream;
begin
  stream := TFileStream.Create(AFilename, fmOpenRead, fmShareDenyWrite);
  try
    stream := TDecompressionStream.Create(stream);
    AList.ReadFromStream(stream);
  finally
    stream.Free;
  end;
end;

Shubit 13. Apr 2010 09:43

Re: Speichern eines großen Arrays
 
Vielen Dank für das ausführlich Beispiel, aber du du lädst beim Öffnen der Vokabelliste doch alle Vokbabeln in einer Objektliste und die wiederrrum liegt irgendwo im Arbeitsspeicher. Ich wollte aber eigentlich nicht alle Vokabeln in den Arbeitsspeicher laden, da es eigentlich nicht nötig sein würde.
Vielmehr würde mir in den meisten Fällen ein einfacher Zugriff auf eine einzelne Vokabl reichen. Vermutlich kann man auch nur einen bestimmten Abschnitt des Streams laden, aber an der Stelle denk ich ist dann die Datenbank die für mich leichtere Methode...

himitsu 13. Apr 2010 10:11

Re: Speichern eines großen Arrays
 
Bloß noch was zum Array, also wenn man die Datenbank nicht möchte.

Delphi-Quellcode:
TVocabulary = record
  Language1: String[100];
  Language2: String[100];
  Note: String[100];
  AddedOn: TDateTime;
  LastTraining: TDateTime;
  Chance: Single;
  Mistakes: Integer;
  Trials: Integer;
  Box: Integer;
end;
TVocabularyArray = Array[0..0] of TVocabulary;
PVocabularyArray = ^TVocabularyArray;
Die Größe des "reellen Arrays wird dann über Dateigröße/Recordgröße berechnet. (ein Length/High gibt es hier ja nicht)

Und jetzt via MMF (Memory Mapped File) die Datei laden und einfach den Array-Pointer drüberlegen.
Somit liegt der Großteil der Datei nicht im RAM und es werden nur die Teile automatisch in den Speicher gemappt, auf welche zugegriffen wird.
Es belegt zwar im Programm immernoch etwa genauso viel "virtuellen" Speicher, wie das "normale" dynamische Array, aber im Windows wird nur ein Bruchteil das Speichers verbraucht, da große Teile einfach in der Datei verbleiben können.

Blup 13. Apr 2010 12:16

Re: Speichern eines großen Arrays
 
Zitat:

Zitat von Shubit
Vielen Dank für das ausführlich Beispiel, aber du du lädst beim Öffnen der Vokabelliste doch alle Vokbabeln in einer Objektliste und die wiederrrum liegt irgendwo im Arbeitsspeicher. Ich wollte aber eigentlich nicht alle Vokabeln in den Arbeitsspeicher laden, da es eigentlich nicht nötig sein würde.
Vielmehr würde mir in den meisten Fällen ein einfacher Zugriff auf eine einzelne Vokabl reichen. Vermutlich kann man auch nur einen bestimmten Abschnitt des Streams laden, aber an der Stelle denk ich ist dann die Datenbank die für mich leichtere Methode...

Wenn dein Projekt tatsächlich nicht den Zugriff auf alle Elemente braucht, ist natürlich eine Datenbank besser.
Allerdings hätte es dann auch keine Geschwindigkeitsprobleme mit dem "File of ..." gegeben (ein Memory Mapped File wäre eine elegantere Variante, funktioniert aber auch nicht mit Strings dynamischer Länge).
Deshalb befürchte ich, daß die Geschwindigkeitsprobleme durch den Einsatz eines Datenbankservers nicht gelöst werden. Die Daten werden vieleicht nicht in den Arbeitsspeicher deiner Anwendung geladen, aber auf Grund der Art deiner Datenabfragen in den Arbeitsspeicher des Datenbankservers. Aber berichte uns von deinen Erfahrungen, sobald dein Projekt soweit ist.

himitsu 13. Apr 2010 12:46

Re: Speichern eines großen Arrays
 
Zitat:

Zitat von Blup
aber auch nicht mit Strings dynamischer Länge

Die gibt es in dem vorgegebenem Record eh nicht. :stupid:,
sonst wäre es mit der "file of" auch nicht gegangen.

Shubit 13. Apr 2010 16:19

Re: Speichern eines großen Arrays
 
wenn du meinen ersten Post gelesen hättest, wüsstest du das mich dies durchaus stört. Dynamische Array wären mir deutlich lieber ...

Also die Datenstruktur muss im Grunde genommen 3 Aufgaben erfüllen:
1. Vokabelabfrage: Dazu würde ich bei der Datenbank den aktuellen Eintrag einmal in ein TVocabulary laden (mit dynamischen arrays) und beim Wechsel zur nächsten Vokabel dann wieder von TVocabulary in die Datenbank speichern.
2. Vokabelliste bearbeiten: Hier geht es im Wesentlichen darum einen Teil der Vokabeln in einer Listbox oder einem Grid (überleg ich mir dann noch) darzustellen und diese dann über zusätzliche Editfelder zu bearbeiten. Dabei wird in der Regel auch immer nur eine Vokabel geladen (außer beim löschen von mehreren Eintrag gleichzeitig).
3. Statistik: Einfach nur ein paar durchschnittliche Werte (Anzahl Fehler etc.), könnte man entweder jedes mal neu bestimmen oder halt entsprechende Variablen mitführen die man dann immer schön aktuell halten muss.


Alle Zeitangaben in WEZ +1. Es ist jetzt 10:49 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz