Jak poprawnie zapisac record do pliku,

0

Hej,

//-> lv1 baza
r_items = record
  pusty:byte; //czy wolny mozna uzyc? bo np skasowano - nie kasujemy lini tylko ustwiamy stan na 0
  Caption:string;
  Subitems:TStringList;
end;

r_lv1 = record
  item:array of unit1.r_items;
end;
//<- lv1 - baza

chce zapisać record fv_lv1 do pliku a następnie go odczytać.

w record fv_lv1 znajdują się tysiące rekordów fv_lv1.item

pomoże ktoś jak zrobić to poprawnie?

Myślałem o TmemoryStream i Stream, ale cos MI nie wychodzi.

przykład o co Mi chodzi:

var
  Stream: TStream;

begin
  Stream:= TFileStream.Create(unit1.lok1+'000test.ph', fmCreate);
  try
     Stream.WriteBuffer(fv_lv1, SizeOf(fv_lv1) * length(fv_lv1));
  finally
     Stream.Free;
  end;
4

Nie możesz zapisać całego rekordu za jednym razem, bo zawartość rekordu nie tworzy ciągłego bloku pamięci. Masz tam pole typu String, które de facto jest wskaźnikiem, a także referencję TStringList, która tym bardziej jest wskaźnikiem. A skoro tak, to tym bardziej nie możesz w ten sposób zapisać całej macierzy.

Jeśli chcesz to możesz użyć klasy TFileStream, ale pola każdego elementu macierzy musisz zapisywać ręcznie. Przy czym jeśli masz pole typu String, to aby dało się później wczytać ciągi znaków z pliku, należy dla każdego ciągu zapisać najpierw jego długość (w bajtach), a dopiero potem jego faktyczną zawartość.

Podsumowując, najpierw powinieneś zapisać liczbę elementów w tablicy, tak aby algorytm odczytujący wiedział ile iteracji wykonać. Następnie w pętli iterujesz po wszystkich elementach tablicy i ręcznie zapisujesz każde pole do strumienia. Najpierw pole bajtowe, następnie długość ciągu Caption, potem ciąg Caption. Ostatnie pole, czyli SubItems musisz zapisać podobnie — najpierw liczba ciągów listy, następnie w pętli każdy ciąg z listy (czyli jego długość i wartość, tak jak w przypadku Caption).

Przy czym jeśli plik z bazą może być przenoszony pomiędzy komputerami, to na samym początku pasuje zapisać informację na temat kolejności bajtów (tzw. endianess), tak aby na innej architekturze CPU odczyt mógł być wykonany poprawnie. A jeśli to plik wyłącznie lokalny i nieprzenośny, to temat kolejności bajtów możesz olać.

Patryk Winner napisał(a):

Myślałem o TmemoryStream i Stream, ale cos MI nie wychodzi.

To co robisz w tym kodzie, który podałeś, nie ma żadnego sensu. Chodzi mi o tworzenie instancji — skoro tworzysz instancję TFileStream, to zmienną zadeklaruj jako TFileStream. Mam wrażenie, że kompletnie nie wiesz co robisz, tak jakby brakowało Ci podstaw z zakresu programowania obiektowego (i nie tylko obiektowego).

I na litość boską, zmień nazwy typów danych i modułów, bo mi pot na czoło wychodzi jak widzę moduł nazwany unit1 czy typ danych r_items, który nie dość, że nic nie mówi o swoim przeznaczeniu, to w dodatku nie posiada wręcz obowiązkowego prefiksu T w nazwie… :/

1

ale co to za rekord? Po nazwie fv_lv1 świta mi coś jakbyś chciał jakieś faktury i ich linie w tym trzymać. Robisz to po to aby coś zrobić z tymi danymi, np. jakąś konwersję? Bo chyba nie chcesz tak przechowywać faktur czy też innych dokumentów?

0
robertz68 napisał(a):

ale co to za rekord? Po nazwie fv_lv1 świta mi coś jakbyś chciał jakieś faktury i ich linie w tym trzymać. Robisz to po to aby coś zrobić z tymi danymi, np. jakąś konwersję? Bo chyba nie chcesz tak przechowywać faktur czy też innych dokumentów?

+1
Jeszcze dużo, duuuużo nauki, zanim przyjdzie pisać "program do faktur"

A druga chorość jaką z identyfikatorów można domniemać, to lv1 ... czyżby ListView1 ?
Chore nacelowanie się na widgety GUI jako kontenery danych ???

@Patryk Winner
To co chcesz zrobić, niekiedy jest nazywane serializacja / serialisation

0

błądzimy.., potrzebuje tylko skutecznie odczytać plik który wcześniej zapisuje zrzut z rekodru.

tak zasępiłem komponent listview rekordem fv_lv1, wszystko działa rewelacyjnie, ale mam problem z czasem odczytu danych po uruchomieniu programu, trwa za długo.

Zapis trwa momentalnie dodaje separatory i wszystko zapisuje do pliku.

Ale odczyt po Pos i Copy trwa zdecydowanie za długo.

obecnie to wygląda tak:

i_pos:=Pos(s_sepend,s_linia);

if i_pos < 1 then //nie ten plik pomin!
  begin
    exit;
  end;

while i_pos > 0 do
  begin
    application.ProcessMessages;
    i_pos:=Pos(s_sepend,s_linia);

    s_dane:=Copy(s_linia,1,i_pos-1);
    Delete(s_linia,1,i_pos+1);

    i_pos2:=Pos(s_seps,s_dane);
    g[0]:=Copy(s_dane,1,i_pos2-1);
    Delete(s_dane,1,i_pos2+1);
    i_pos2:=Pos(s_seps,s_dane);
    g[1]:=Copy(s_dane,1,i_pos2-1);
    Delete(s_dane,1,i_pos2+1);
    i_pos2:=Pos(s_seps,s_dane);
    g[2]:=Copy(s_dane,1,i_pos2-1);
    Delete(s_dane,1,i_pos2+1);
    i_pos2:=Pos(s_seps,s_dane);
    g[3]:=Copy(s_dane,1,i_pos2-1);
    Delete(s_dane,1,i_pos2+1);
    i_pos2:=Pos(s_seps,s_dane);
    g[4]:=Copy(s_dane,1,i_pos2-1);
    Delete(s_dane,1,i_pos2+1);
    i_pos2:=Pos(s_seps,s_dane);
    g[5]:=Copy(s_dane,1,i_pos2-1);
    Delete(s_dane,1,i_pos2+1);
    i_pos2:=Pos(s_seps,s_dane);
    g[6]:=Copy(s_dane,1,i_pos2-1);
    Delete(s_dane,1,i_pos2+1);
    i_pos2:=Pos(s_seps,s_dane);
    g[7]:=Copy(s_dane,1,i_pos2-1);
    Delete(s_dane,1,i_pos2+1);
    i_pos2:=Pos(s_seps,s_dane);
    g[8]:=Copy(s_dane,1,i_pos2-1);

     if g[1] = '' then //pomijaj jesli puste!
       continue;

    //-> dodaj lv1
    r_idx_lv1:=anc_fv1.f1_lv1_add;
    unit1.fv_lv1.item[r_idx_lv1].pusty:=1; //zajety!

        unit1.fv_lv1.item[r_idx_lv1].Caption:=g[0];
        unit1.fv_lv1.item[r_idx_lv1].Subitems.Add(g[1]);
        unit1.fv_lv1.item[r_idx_lv1].Subitems.Add(g[2]);
        unit1.fv_lv1.item[r_idx_lv1].Subitems.Add(g[3]);
        unit1.fv_lv1.item[r_idx_lv1].Subitems.Add(g[4]);
        unit1.fv_lv1.item[r_idx_lv1].Subitems.Add(g[5]);
        unit1.fv_lv1.item[r_idx_lv1].Subitems.Add(g[6]);
        unit1.fv_lv1.item[r_idx_lv1].Subitems.Add(g[7]);
        unit1.fv_lv1.item[r_idx_lv1].Subitems.Add(g[8]);
    //<- dodaj lv

 end;

to działa bezbłędnie, ale odczyt jest za wolny!

1

Dokładnie to co chcesz zrobić to serializacja.
http://docwiki.embarcadero.com/RADStudio/Sydney/en/Serializing_User_Objects

1

Zapis trwa momentalnie dodaje separatory i wszystko zapisuje do pliku.

Ale odczyt po Pos i Copy trwa zdecydowanie za długo.

Zerknij na funkcję Split zamiast rzeźbić tymi Pos-ami.

0

Split oraz ExtractStrings działają wolniej od Mojego rozwiązania.
Obecnie to najszybsze rozwiązanie wczytywania rekordów jakie udało Mi się osiągnąć.

Zastanawiam się jeszcze nad serializacja. Czy mógłby ktoś Mi z tym pomóc na podstawie moich rekordów ?

Dziękuje

1

Może chociaż zamiast Delete użyj PosEx? Nigdy nie testowałem na dużych zbiorach danych ale Delete może zdrowo spowalniać operacje?

3

Nie chce mi się zapoznawać z zawiłościami Twojego kodu @Patryk Winner, dlatego pokażę Ci przykładowy kod odpowiedzialny za zapis całej macierzy do pliku oraz jej odczyt. Nie wiem którego Delphi używasz, ja nie używam żadnego, dlatego podam przykład napisany we Free Pascalu.

Przyjmijmy, że struktury danych wyglądają tak (takie same jak Twoje, ale sformatowane):

type
  TItem = record
    Empty: Byte;
    Caption: String;
    Subitems: TStringList;
  end;

type
  TItems = array of TItem;

Prosta struktura zawierająca pola z jednym bajtem, ciągiem znaków i listą, a także zwykła dynamiczna macierz takich rekordów. Aby zapisać do pliku całą macierz, należy najpierw zapisać liczbę rekordów, a potem każdy z nich i dla każdego z nich, każde pole z osobna:

procedure SaveItemsToFile(const AFileName: String; const AItems: TItems);
var
  Output: TFileStream;
  ItemIndex, SubitemIndex: Integer;
begin
  Output := TFileStream.Create(AFileName, fmCreate or fmShareDenyWrite);
  try
    Output.WriteDWord(Length(AItems));

    for ItemIndex := Low(AItems) to High(AItems) do
      with AItems[ItemIndex] do
      begin
        Output.WriteByte(Empty);
        Output.WriteAnsiString(Caption);
        Output.WriteDWord(Subitems.Count);

        for SubitemIndex := 0 to Subitems.Count - 1 do
          Output.WriteAnsiString(Subitems[SubitemIndex]);
      end;
  finally
    Output.Free();
  end;
end;

Jak pisałem wcześniej — najpierw zapisuje się liczbę rekordów, następnie rekordy. Dla każdego rekordu najpierw bajtowa flaga pustości, potem ciąg znaków (ja mam do dyspozycji metodę WriteAnsiString, która zapisuje najpierw długość ciągu, a następnie jego wartość), następnie liczbę subitemów i każdy subitem z osobna (też jako para długość-wartość).

Odczyt wygląda podobnie, ale z tą różnicą, że trzeba nadać rozmiar macierzy (raz!), no i w każdym odczytywanym rekordzie należy stworzyć instancję TStringList. Reszta pozostaje bez zmian.

procedure LoadItemsFromFile(const AFileName: String; out AItems: TItems);
var
  Input: TFileStream;
  ItemsCount, SubitemsCount: Integer;
  ItemIndex, SubitemIndex: Integer;
begin
  Input := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
  try
    try
      ItemsCount := Input.ReadDWord();
      SetLength(AItems, ItemsCount);

      for ItemIndex := 0 to ItemsCount - 1 do
        AItems[ItemIndex].Subitems := TStringList.Create();

      for ItemIndex := 0 to ItemsCount - 1 do
        with AItems[ItemIndex] do
        begin
          Empty := Input.ReadByte();
          Caption := Input.ReadAnsiString();
          SubitemsCount := Input.ReadDWord();

          for SubitemIndex := 0 to SubitemsCount - 1 do
            Subitems.Add(Input.ReadAnsiString());
        end;
    except
      for ItemIndex := 0 to ItemsCount - 1 do
        AItems[ItemIndex].Subitems.Free();

      SetLength(AItems, 0);
      raise;
    end;
  finally
    Input.Free();
  end;
end;

W razie gdyby wystąpił błąd podczas odczytywania, referencje Subitems zostaną zwolnione, rozmiar macierzy ustawiony na 0, a wyjątek wydostanie się z procedury, aby można było na zewnątrz niej zareagować na błędy odczytu.

Działania kodu nie sprawdzałem — tylko to czy się kompiluje, więc miej to na uwadze.

0

Delphi 10.4 kod nie kompiluje sie:

niestety TFileStream nie ma takich właściwości

Checking project dependencies...
Compiling Project1.dproj (Debug, Win32)
[dcc32 Error] Unit1.pas(39): E2003 Undeclared identifier: 'WriteDWord'
[dcc32 Error] Unit1.pas(44): E2003 Undeclared identifier: 'WriteByte'
[dcc32 Error] Unit1.pas(45): E2003 Undeclared identifier: 'WriteAnsiString'
[dcc32 Error] Unit1.pas(46): E2003 Undeclared identifier: 'WriteDWord'
[dcc32 Error] Unit1.pas(49): E2003 Undeclared identifier: 'WriteAnsiString'
[dcc32 Fatal Error] Project1.dpr(5): F2063 Could not compile used unit 'Unit1.pas'
Failed
Elapsed time: 00:00:00.4
1

Rany boskie… no to skorzystaj z tych, które ma! Write(Buffer) i Read(Buffer) wystarczą w zupełności.

Poza tym to są metody, nie właściwości.

1 użytkowników online, w tym zalogowanych: 0, gości: 1