Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Problem beim Datei auslesen in ein Array (https://www.delphipraxis.net/77592-problem-beim-datei-auslesen-ein-array.html)

Quick_silver 21. Sep 2006 16:47


Problem beim Datei auslesen in ein Array
 
Ich will eine Datei auslesen.
Dafür habe folgenden Code:

Inhalt der Datei:
02 00 00 00 01 00 <ID 21 A1 07 00> <Stringlänge 00 13> 00 <string>
<ID> <Stringlänge> 00 <string>
<ID> <Stringlänge> 00 <string>
etc...

Delphi-Quellcode:
    fs.Seek(6,0); //Die ersten Bytes überspringen

    fs.Read(id, 4);
    fs.Seek(1, 1); //Ein byte überspringen (gibt es ne bessere lösung?)
    fs.Read(Len, 2);

    SetLength(text, Len);
    fs.Read(PChar(text)^, Len);

    //daten[i].id := id;
    //daten[i].text := text;
DIe Daten will ich in dieses Array:
Delphi-Quellcode:
type
  TDatensatz = record
    ID: integer;
    Text: AnsiString;
  end;

  TDatenArray = array of TDatensatz;
Und das ist mein versuch der imemr irgnedwleche Speicherzugriffsfehler auswirft und das Programm crasht:

Delphi-Quellcode:
procedure TForm1.ReadData(datei : string; var daten : TDatenArray );
var
fs : TStream;
i, id, Len : Integer;
text : string;
begin
  fs := TFileStream.Create(datei, fmOpenReadWrite);

  fs.Seek(6,0);
  i := 0;

  while fs.Position < fs.Size do
  begin
    inc(i);
    SetLength( daten, i );

    fs.Read(id, 4);
    fs.Seek(1, 1); //Will ich weg haben
    fs.Read(Len, 2); //Wieso nur 2 bytes?

    SetLength(text, Len);
    fs.Read(PChar(text)^, Len);

    daten[i].id := id;
    daten[i].text := text;

    //Immerhin funktionniert die schleife überhaupt erst mit der folgenden Zeile. *schulterzuck*
    //Ohne diel iest er nur den ersten eintrag.
    ListBox.Items.Add( IntToStr(fs.Position) +'<'+IntToStr(fs.Size) );
  end;
  fs.Free;
end;
Wie kann ich das ganze eleganter lösen?

panzerfischer 21. Sep 2006 16:52

Re: Problem beim Datei auslesen in ein Array
 
was soll denn in der datei stehen (bitte nicht in einsen und nullen(auch kein hexcode)) man muss es ja nicht schlimmer machen als es ist

Quick_silver 21. Sep 2006 16:57

Re: Problem beim Datei auslesen in ein Array
 
Eine ID (Integer) und ein String der zu dieser ID gehört.
Davon einige tausend.
Könnte man aus dem gepostet Code auch erahnen :/

Das auslesen funktioniert ja auch grob so. Also die Variablen ID und Text werden schon richtig gefüllt.

panzerfischer 21. Sep 2006 17:03

Re: Problem beim Datei auslesen in ein Array
 
also die typdeklaration ist schon mal gut, ansonsten, warum ließt du nicht die datei im ganzen aus?

z.b. so:
Delphi-Quellcode:
type TDatensatz = record ID: integer; Text: AnsiString; end;

var TDatenArray = array[0..viel] of TDatensatz;
     TDatenfile = file of datensatz;

..

assignfile(TDatenfile,'pfad/datei');
{$I-} //eingabe/ausgabe fehler werden ignoriert
reset(TDatenfile);
{$I+}
if IoResult = 0 then begin
  i:=0;
  while not eof do {end of file}
    read(TDatenfile,TDatenarray[i]);
  inc(i);
end;
closefile(TDatenfile);
speichern funktioniert ähnlich. hat halt den nachteil, dass du die größe vom array festlegen musst, weil du vorher nicht weißt wieviel drinne steht
außerdem musst du die länge des strings festlegen, weil der rechner ja sonst nicht weiß, wie lang die datei nun ist

Quick_silver 21. Sep 2006 17:09

Re: Problem beim Datei auslesen in ein Array
 
Also
var TDatenArray = array[0..viel] of TDatensatz;
kann ich schlecht nehmen, ich muss schon vorher rausfinden wie viele DAtensätze ich habe, oder das array dynamisch während des auslesens erweitern.

Zudem wird dein Vorschlag wohl 6 bytes zu fürh anfangen zu lesen, oder nicht?

Die Länge der Text Variable muss ebenfalls vor jedem Eintrag neu fest gelegt werden. Da die Strings ja nicht immer gleich lang sein. Eigentlich so gut wie nie.

panzerfischer 21. Sep 2006 17:30

Re: Problem beim Datei auslesen in ein Array
 
eigendlich nicht, da die 6byte zum kopf gehören, und nicht ausgelesen werden, bei der größe der strings musst du wissen, mit welcher größe die gespeichert wurden, damit es nicht zu verschiebungen kommt, kann aber auch sein, das der computer erkennt, wie groß der string ist, da nomalerweise vor dem ersten buchstaben die länge steht, musst du mal ausprobieren

ein dynamisches array erweitern könnte gut funktionieren

woher kommt die datei, bzw. womit hast du sie erstellt?

SirThornberry 21. Sep 2006 17:35

Re: Problem beim Datei auslesen in ein Array
 
Zitat:

Zitat von panzerfischer
also die typdeklaration ist schon mal gut, ansonsten, warum ließt du nicht die datei im ganzen aus?

z.b. so:
Delphi-Quellcode:
type TDatensatz = record ID: integer; Text: AnsiString; end;

var TDatenArray = array[0..viel] of TDatensatz;
     TDatenfile = file of datensatz;

..

assignfile(TDatenfile,'pfad/datei');
{$I-} //eingabe/ausgabe fehler werden ignoriert
reset(TDatenfile);
{$I+}
if IoResult = 0 then begin
  i:=0;
  while not eof do {end of file}
    read(TDatenfile,TDatenarray[i]);
  inc(i);
end;
closefile(TDatenfile);
speichern funktioniert ähnlich. hat halt den nachteil, dass du die größe vom array festlegen musst, weil du vorher nicht weißt wieviel drinne steht
außerdem musst du die länge des strings festlegen, weil der rechner ja sonst nicht weiß, wie lang die datei nun ist

Das geht nicht weil in TDatensatz ein Ansistring enthalten ist was widerum nur ein Pointer ist.

@Quick_silver: Wenn Len bei dir ein Integer (4 Byte) ist und du mit
Delphi-Quellcode:
fs.Read(Len, 2);
nur 2 Byte in diese Variable einliest sind die anderen 2 Byte weiterhin undefiniert. Das heißt in den restlichen 2 Byte steht weiter irgendwelcher Zufallsmüll. Entweder nimmst du als Typ "word" oder initialisierst die Variable vorher mit "0".
Am besten du debugst das ganze mal und prüfst nach jedem schritt welche Werte die Variablen haben. Dann weißt du auch wo du daneben greifst.

Der_Unwissende 21. Sep 2006 17:39

Re: Problem beim Datei auslesen in ein Array
 
Hi,
kannst du dann vielleicht eine solche Datei posten?

Ansonsten sehe ich nicht ganz warum dein Code nur so arbeiten sollte.
Delphi-Quellcode:
var List : TList;
    fs : TFileStream;
    buffer : ^TDatensatz;
    length : Integer;
begin
  list := Tlist.Create;
  ...

  try
    while (fs.Position < fs.Size) do
    begin
      new(buffer);
     
      fs.Read(buffer.id, 4);
      fs.Position := fs.Position + 1;
      fs.Read(length, 2);
      fs.Position := fs.Position + 1;
   
      setLength(buffer.Text, length);
     
      fs.Read(buffer.Text, length);

      list.Add(buffer);
    end;
  finally
    fs.Free;
  end;
end;
So sollte das ganze eigentlich klappen (wenn die Datei so aufgebaut ist wie du sagst (und natürlich ich keinen Fehler gemacht habe). An sich empfiehlt sich hier eine Liste. Intern wird zwar auch ein Array verwendet und du kannst auch per Index wahlfrei auf bestimmte Elemente zugreifen, aber die Größenanpassung ist hier von Delphi übernommen. Dein Array immer nur um 1 Element zu erweitern ist sehr schlecht. Es kostet Zeit neuen Speicher zu allozieren und das alte Array dort reinzukopieren. Eine List alloziert gleich eine vielzahl von weiteren Elementen. Sind diese neu allozierten Plätze zu einem gewissen Grad gefüllt, wird wieder um ein gutes Stück vergrößert. Hast du einen Overhead (an Zeit) von O, dann teilt der sich damti auf die Anzahl n der allozierten Elemente auf, ist also in deinem Fall 0/1 = O und das in jeder Iteration, im Falle der Liste O/n und n ist halt von Delphi gewählt (und das passiert automatisch).

Gruß Der Unwissende

[edit]
Delphi Tag geöffnet, quote geschlossen...
[/edit]

panzerfischer 21. Sep 2006 17:42

Re: Problem beim Datei auslesen in ein Array
 
ansistring sagt doch nur aus, das max. 2^31buchstaben gespeichert werden können. vor jedem string steht die länge des strings also die ersten 4byte in diesem fall, die auskunft über die länge geben.

könnte sogar so sein, das es sich bei dmn integer wert in seiner datei, um die länge des strings handelt, ich weiß ja nicht genau, was das für ne file ist

SirThornberry 21. Sep 2006 17:45

Re: Problem beim Datei auslesen in ein Array
 
Zitat:

Zitat von panzerfischer
ansistring sagt doch nur aus, das max. 2^31buchstaben gespeichert werden können. vor jedem string steht die länge des strings also die ersten 4byte in diesem fall, die auskunft über die länge geben.

könnte sogar so sein, das es sich bei dmn integer wert in seiner datei, um die länge des strings handelt, ich weiß ja nicht genau, was das für ne file ist

Ansistring sagt aus das es String mit variabler Länge ist und somit nur ein Pointer. Bei einem Ansistring liegen die eigentlichen Daten also irgendwo und nicht hinter der Adresse der Variablen.
Du kannst ja mal SizeOf(DeineAnsiStringVariable) ausgeben lassen. Du wirst immer 4 zurück bekommen weil der AnsiString eben nur 4 Byte groß ist (bei einem 32bit compiler)

panzerfischer 21. Sep 2006 18:03

Re: Problem beim Datei auslesen in ein Array
 
laut delphi hilfe ist der string max 2^31 zeichen lang (die länge als zahlenwert ist somit 4byte, aber nicht der string selbst)

mal abgesehen, geht es in diesem fall um die datei arbeit, nicht um speicherverwaltung, und in so ner datei, steht alles hintereinander, damit der rechner weit, welcher string wie lang ist, schreibt er die läge vorneweg.

es gab auch solche eol zeiger (end of line) aber ich glaube die haben sich auf textdateien bezogen, also dateien voller char's

ich meine 2^31 zeichen, das sind schon einpaar zeilen, glaube nicht, das seine strings so groß sind, im allgemeinen fall würde denke ich mal auch ein shortstring reichen, das sind auch schon 255 zeichen

Quick_silver 21. Sep 2006 18:30

Re: Problem beim Datei auslesen in ein Array
 
Hat nix mit dme Ansistring zu tun. Ich habe das Problem soweit gelößt.
Ich kann jetzt die ganze Datei auslesen:

Delphi-Quellcode:
procedure TForm1.ReadData(datei : string; var daten : TDatenArray );
var
fs : TStream;
i, id, check : Integer;
Len : Short;
text : string;
begin

  fs := TFileStream.Create(datei, fmOpenReadWrite);
  ProgressBar.Max := fs.Size;
  ProgressBar.Position := 0;

  fs.Seek(6,soFromBeginning);
  i := 0;

  repeat
    inc(i);
    SetLength( daten, i ); // datenarray lenge anpassen

    check := fs.Read(id, SizeOf(id)); //Id auslesen
    if check < SizeOf(id) then        //Wenn es keine mehr gab abbrechen
      break;

    fs.Seek(1, soFromCurrent);   //1 byte überspringen
    fs.Read(Len, SizeOf(Len));   //Lenge des Strings
    SetLength(text, Len); //Stringlänge setzten
    fs.Read(PChar(text)^, Len); //String auslesen

    daten[i-1].id := id;
    daten[i-1].text := text;
    //Label1.Caption := IntToStr(i);
    //Label1.Update;
    ProgressBar.StepBy(7+Len);

  until check < SizeOf(id);

  fs.Free;
end;
Für Len habe ich nen Short genommen, welcher nun nurnoch ähm 2Bye? Groß ist. Passt auf jedenfall.
Das unterbrechen habe ich auch ganz gut hinbekommen. Position und Size soll angeblich stark die Performence reduzieren.

Wobei wir auch genau beim nächsten Problem sind.
Meine Datei hat (die kleienre Variante) 18.000 Einträge.

Frage 1. Wieso liest er am anfang shcneller aus als zum Ende hin?
Frage 2. Was gibt es für eine Alternative zu meinem Array die daten schneller / besser zu speichern?
Und sonst, wo kann man noch Optimieren? Es dauert im moment echt arg lange :(

panzerfischer 21. Sep 2006 18:34

Re: Problem beim Datei auslesen in ein Array
 
von was wurde denn die datei/en erstellt? und in welchem format, das würde die sache enorm vereinfachen,

Quick_silver 21. Sep 2006 18:55

Re: Problem beim Datei auslesen in ein Array
 
Von was weiss ich nicht, also von den EA entwicklern wahrscheinlich.
Das ist ne Sprachdatei in der Strings gespeichert sind.
Der Client bekommt ne Nummer und gibt nen String aus. Was den Traffic verringert.
Geht um Ultima Online.

Spielt aber keine Rolle. Das format ist so wie ichs beschrieben habe. Mehr weiss ich nicht.

panzerfischer 21. Sep 2006 19:15

Re: Problem beim Datei auslesen in ein Array
 
wie oft willst du auf diese datei zugreifen, bzw, was hast du damit vor, willst bestimmte sachen ändern, und die wieder in das selbe format schreiben oder wie oder was?

Quick_silver 21. Sep 2006 19:17

Re: Problem beim Datei auslesen in ein Array
 
Kann es sein das die Daten über das Array sehr sehr platzverschwenderisch im Arbeitsspeicher verteilt werden? Anders kann ich mir das nicht vorstellen.
Aber wleche bessere Möglichkeit zur Verwlaung gibt es? Die Daten sleber sind nur 600KB bis 1,6MB groß... Das kann doch nicht so irrsinnig viel Speicher fressen.



Ich will:

Also ich habe 2 dieser Dateien. Ich will im endeffekt die eine in die andere Mergen.
Aus den 2 Dateien will ich eine erstellen in der Alle Strings aus beiden Dateien stehen. Falls in Datei1 einer vorhanden ist der in Datei2 auch vorhanden ist soll dieser überschrieben werden.

D.h. ich muss die ausgelesenn Datensätze auch sortieren können. Wofür sie dann ja wohl am besten im Arbeitsspeicher stehen sollten.

panzerfischer 21. Sep 2006 19:24

Re: Problem beim Datei auslesen in ein Array
 
nimm vieleicht mal was anderes als ansistring, das ist nunmal ein speicherfrsser

Khabarakh 21. Sep 2006 19:35

Re: Problem beim Datei auslesen in ein Array
 
Delphi-Quellcode:
inc(i);
SetLength( daten, i );
Uuuh, schwerer Speicher- und Performancefresser. Kann es sein, dass die Anzahl der Items in den ersten sechs Byte der Datei steckt? Wenn ja, setze das Array einmalig auf diese Größe, ansonsten: Delphi-Referenz durchsuchenTList.

Quick_silver 21. Sep 2006 20:22

Re: Problem beim Datei auslesen in ein Array
 
Array imemr um 1000 statt 1 erhöhen reicht schon.
Aber die TList ist noch besser.
Hat aber eher den Grund das ich damit die Daten dann auch sortieren kann. Die Geschiwindigkeit ist nun angemessen.

Über das Soriteren (wovon ich noch keine Ahnung hab) recherchier ich erstmal im foum. Muss hier ja nicht auch noch breit getreten werden^^

Edit:
Und danke an alle die Geholfen haben.

Hier der Code, vll hilft es ja nochmal:
Delphi-Quellcode:
type
  TDatensatz = record
    ID: integer;
    Text: AnsiString;
  end;

  TDatenPointer = ^TDatensatz;


procedure TForm1.btnReadClick(Sender: TObject);
var
daten1, daten2 : TList;
i : Integer;
p : TDatenPointer;
begin

  daten1 := Tlist.create;
  daten2 := Tlist.create;

  Label1.Caption := 'Lese Datei: '+PathFrom.Text;
  Label1.Update;
  ReadData(PathFrom.Text, daten1);
  Label1.Caption := 'Lese Datei: '+PathTo.Text;
  Label1.Update;
  ReadData(PathTo.Text, daten2);
  //Merge - Muss nurnoch sortiert werden
  Label1.Caption := 'Merge...';
  Label1.Update;
  MergeData( daten1, daten2 );
  //Save - Fehlt noch


  for i := 0 to daten1.count-1 do
  begin
    p := TDatenPointer( daten1.items[ i ] );
    dispose( p );
  end;

  for i := 0 to daten2.count-1 do
  begin
    p := TDatenPointer( daten2.items[ i ] );
    dispose( p );
  end;

  Daten1.Free;
  Daten2.Free;

end;

procedure TForm1.ReadData(datei : string; var daten : TList );
var
fs : TStream;
p : TDatenPointer;

i,j, id, check : Integer;
Len : Short;
text : string;
begin
  fs := TFileStream.Create(datei, fmOpenReadWrite);
  ProgressBar.Max := fs.Size;
  ProgressBar.Position := 0;

  fs.Seek(6,soFromBeginning);
  i := 0;
  j := 1000;

  repeat
    inc(i);

    //Array verlängern
    //if Length( daten ) <= i then
      //SetLength( daten, Length( daten )+3000);


    check := fs.Read(id, SizeOf(id)); //Id auslesen
    if check < SizeOf(id) then        //Wenn keinem ehr gab abbrechen
      break;

    fs.Seek(1, soFromCurrent);   //1 byte überspringen
    fs.Read(Len, SizeOf(Len));   //Lenge des Strings
    SetLength(text, Len); //Stringlänge setzten
    fs.Read(PChar(text)^, Len); //String auslesen

    //daten[i-1].id := id;
    //daten[i-1].text := text;

    new( p );
    p.ID := id;
    p.Text:= text;
    daten.add( p );

    //Label1.Caption := IntToStr(i);
    //Label1.Update;
    ProgressBar.StepBy(7+Len);

  until check < SizeOf(id);

  fs.Free;
end;


//Veorläufige Verison, kann sicher noch stark optimiert werden:
procedure TForm1.MergeData( Daten1: TList; Daten2: TList);
var
IDsToAdd : array of integer;
i,j:integer;
found : Boolean;
p, p1, p2 : TDatenPointer;
begin
  ProgressBar.Position := 0;
  ProgressBar.Max := Daten2.Count;

  ListBox.Items.Add('Vor Merge Data1.Count: '+ inttostr(daten1.count));
  ListBox.Items.Add('Vor Merge Data2.Count: '+ inttostr(daten2.count));

  for i := 0 to Daten2.Count-1 do
  begin //Data 2 durchgehen
    p2 := TDatenPointer( daten2.items[ i ] );
    ProgressBar.StepBy(1);

    //Suchen ob data2 in data1 vorhanden ist
    found := false;
    for j:=0 to Daten1.Count-1 do
    begin
      p1 := TDatenPointer( daten1.items[ j ] );
      if p1.ID = p2.ID then
      begin
        found := true;
        break;
      end;
    end;

    if found <> true then //Die ID ist in Data1 NICHT vorhanden
    begin
      //Dann muss sie in Data 1 eingefügt werden...
      new( p );
      p.ID := p2.ID;
      p.Text:= p2.Text;
      daten1.add( p );
    end;
  end;

    ListBox.Items.Add('Nach Merge Data1.Count: '+ inttostr(daten1.count));
    ListBox.Items.Add('Nach Merge Data2.Count: '+ inttostr(daten2.count));

    Label1.Caption := 'Merge...Done ... Sort';
    Daten1.Sort(??????????????)


end;
Wem hilft ist gut, wem nicht auch gut und die Sikusion endet dann erstmal hier ;)


Alle Zeitangaben in WEZ +1. Es ist jetzt 14:10 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