Parser pliku tekstowego - dobór odpowiednich typów danych

1

W ostatnich dniach opracowałem sobie typ pliku do przechowywania wszelkich konfiguracji w najzwyklejszym pliku tekstowym w postaci drzewa (podobnie jak XML); Podobnie jak pliki INI struktura mojego pliku jest ściśle określona - poszczególne wartości muszą być w kolejnych liniach pliku, dzięki temu parser klasy do obsługi tych plików skanuje i przetwarza informacje linia po linii i tworzy drzewo struktury w pamięci odpowiednio operując na klasach; Zawartość pliku podczas parsowania całkowicie rozkładana jest na poszczególne elementy (tworzone jest drzewo obiektów), a nie przechowywana w liście typu TStrings/TStringList;

Poniżej przykład zawartości pliku w najprostszej postaci (jedynie do zobrazowania - z jedną właściwością):

file TreeStructInfo name "Settings"
   :: comment
   prop Foo "Bald"
done

gdzie prop to słowo kluczowe oznaczające, że linia zawiera dane właściwości, Foo to nazwa właściwości, Bald to jej wartość, a :: comment to linia komentarza;


Otóż klasa bazowa TCustomTSInfoFile (po części abstrakcyjna) jak i klasa rozszerzona TTSInfoFile (dziedzicząca po klasie bazowej) jest już zaimplementowana, parser poprawnie przetwarza zawartość pliku tworząc drzewo relacji w pamięci; Ogólnie jest już wszystko zaimplementowane i działa poprawnie (debugowanie nie wykazało nieprawidłowości) - klasy są gotowe do użytku;

Jednak póki co nie ma obsługi wartości wieloliniowych... Aby zwiększyć funkcjonalność klasy chciałbym w niej zaimplementować obsługę wartości, które można rozpisać w kilku liniach; Może po kolei - klasa przechowująca informacje na temat pojedynczej właściwości

type
  TTSInfoProperty = class(TObject)
  private
    FName: ANSIString;
    FValue: ANSIString;
    FComment: TStrings;
  private
    procedure SetPropertyName(AName: ANSIString);
    procedure SetPropertyComment(AComment: TStrings);
  public
    constructor Create(const AName: ANSIString; const AValue: ANSIString);
    destructor Destroy(); override;
  public
    property Name: ANSIString read FName write SetPropertyName;
    property Value: ANSIString read FValue write FValue;
    property Comment: TStrings read FComment write SetPropertyComment;
  end;

Jak widać klasa jest malutka - zawiera trzy prywatne pola:

Identyfikator Typ Opis
FName AnsiString Nazwa właściwości
FValue AnsiString Wartość (na razie jednoliniowa)
FComment TStrings Komentarz (może być wieloliniowy)
Do tego dochodzą settery/gettery - one nie mają większego znaczenia, więc nie będę Was zagłębiał w ich przeznaczenie;

Z racji tej, że komentarz do właściwości i innych elementów struktury pliku może być wieloliniowy do jego przechowywania w pamięci wybrałem klasę TStrings; Aby podobnie postąpić z wieloliniową wartością właściwości mogę wykorzystać TStrings i podczas parsowania pliku odpowiednio uzupełniać zawartość list;

Niestety nie sądzę, by wykorzystanie list do przechowywania zarówno komentarzy, jak i wieloliniowych wartości właściwości była dobrym rozwiązaniem; Podobnie jak w plikach INI limit ilośći kluczy nie jest z góry ustalony (nie wliczając limitu pamięci itp.), tak i w tym pliku maksymalna ich liczba nie jest z góry ustalona; W takim wypadku jeśli właściwości będzie niedużo (np. 5) - zajętość pamięci będzie niska, czego w przypadku ich dużej ilości (100 - 200) nie będzie można powiedzieć;

Sądzę, że klasa TStrings będzie w tym wypadku zbyt ciężkim działem, albowiem jej bogate możliwości nie będą praktycznie w ogóle wykorzystane, gdyż oprócz samego gołego przechowywania linii większość metod nie będzie wykorzystana; Dlatego też nie chcę marnować pamięci na coś, co będzie wykorzystane jedynie w 5%;

Klasa TStrings jest bardzo wygodna w użytkowaniu, jedak wolę zamienić wygodę użytkowania (z poziomu kodu) na dużo mniejszą zajętość pamięci; Dlatego też wziąłem na celownik dynamiczną macierz typu AnsiString, która może i nie będzie najwygodniejsza w obsłudze, jednak pozwoli zaoszczędzić pamięć;


W takim wypadku mam mały dylemat: wykorzystać do przechowywania wartości i komentarzy listy czy zastosować w obu przypadkach macierze dynamiczne? Może jakiś inny typ, który teraz nie przychodzi mi do głowy? A może zastosować w obu przypadkach zwykłe łańcuchy AnsiString, a rozpoznawać poszczególne linie dzięki separatorom (choć tego wolałbym nie brać pod uwagę)?

Na wygodzie mi raczej nie zależy, liczy się przede wszystkim szybkość oraz zajętość pamięci; Potrzebuję wskazówki na temat odpowiedniego do tego celu typu;

Całości klasy na razie nie przedstawiam, bo nie ma on większego znaczenia i nie mam problemu z kodem (z implementacją), tylko z doborem typu danych; Proszę o wskazówki, dzięki z góry.

0

Robiłem kiedyś coś podobnego w C++. Do trzymania na ogól nie potrzebnych danych takich jak ten komentarz trzymałem dwie liczby, offset oraz length w pliku - pobierane tylko "na zamówienie".

0

W jakim celu w ogóle parsować komentarze? Wystarczy, że parser je ominie - i tak nie są istotne dla struktury pliku, a mają być jedynie informacją dla osoby edytującej, o ile dobrze rozumiem.

wykorzystać do przechowywania wartości i komentarzy listy czy zastosować w obu przypadkach macierze dynamiczne?

TStringList nie jest przypadkiem zaimplementowane internalsowo tak czy siak jako macierz dynamiczna?

0

Pewnie korzystałeś ze strumieni;

Z punku widzenia programisty komentarze to zbędna rzecz, jednak nie chciałbym ich tracić w trakcie parsowania pliku; Dlatego też jeśli są to mało istotne informacje wolałbym nimi nie obciążać zbytnio algorytmów i pamięci;


EDIT: @Patryk27 - komentarze nie stanowią istotnych informacji dla struktury pliku, ale nie mogę ich tracić; Omijanie podczas parsowania także nie wchodzi w grę, bo podczas zapisu całej struktury drzewa do pliku komentarze zostaną utracone, a tego chciałbym uniknąć;

A co do TStringList - sądzę, że nie jest to macierz, a lista jedno/dwukierunkowa łańcuchów opakowana w klasę i wyposażona w wiele metod, które mi w ogóle nie są potrzebne; Potrzebuję jedynie przechować te komentarze i ew. dać możliwość z poziomu kodu na ich modyfikację.

1

wolałbym nimi nie obciążać zbytnio algorytmów i pamięci.

Dramatyzujesz.
Nie żyjemy w czasach TP; w Lazarusie TStrings.InstanceSize jest równe 32 bajtom - możesz sobie utworzyć tysiące takich obiektów, wypełnić je w dziesiątkach linijek, a wciąż zajętość pamięciowa będzie relatywnie mała.

0

Może i tak, ale wolę podyskutować :]

Chodzi mi tylko o to, czy listy TStrings/TStringList będą do tego celu dobre, czy lepiej wykorzystać coś "lżejszego".

1
  1. Wykorzystaj AnsiString. "Wieloliniowość" to tylko sposób interpretacji. Równie dobrze można tekst wieloliniowy traktować jako jedną wartość i parsować znaki końca linii razem z innymi albo w ogóle wrzucać je na żywca do kontrolek (od biedy).

  2. Po co nowy format konfiguracji kiedy co najmniej dwa UNIWERSALNE są już na tym świecie od kilku lat? (XML & JSON Corp.)

  3. Ze względu na to że każdy z nas ma przyznaną ograniczoną liczbę cykli na tym świecie, proponuję zainteresować się biblioteką superobject.

http://code.google.com/p/superobject/

  1. Gdybyś jednak obstawał przy swoim to i tak komentarze bym pomijał. Przynajmniej w tym wypadku.
1

Ja bym skłaniał się ku prostemu typowi string - możesz w nim trzymać każdy znak, limitu na długość generalnie nie ma, jedynym nieco upierdliwym problemem może stać się ich drukowanie i zamienianie znaku przejścia do nowej linii, ale... to tylko komentarz wypisać i zapomnieć, nie będziesz chyba sortował w nim linii, edytował itp. jeśli naprawdę będzie to konieczne użytkownik klasy sobie opakuje go w tego stringlist'a i przetworzy jak mu wygodnie.

2

Chodzi mi tylko o to, czy listy TStrings/TStringList będą do tego celu dobre, czy lepiej wykorzystać coś "lżejszego".

Generalnie to z TStringList stracisz trochę bajtów pamięci, ale będzie to z góra 8 bajtów na jedną linię tekstu. Do tego będą koszty stałe dla każdego TStringList, jakieś 40bajtów. I do tego koszty stałe wynikające z istnienia implementacji całego TStringlist list, czyli zapewne z ~500bajtów.
Ja bym rozważył:

  1. Czy naprawdę nie masz wystarczająco RAMu aby przejmować się jej zajętością przy wygodzie implementacji, niezawodności i wydajności?
  2. Czy musisz parsować to na linie? Jeżeli nie to właśnie czysty string może być najlepszy. Pamiętaj wtedy o niższej wydajności ewentualnych manipulacji.
  3. Jeżeli TStringList to trochę za dużo, a dynamiczny array za mało, może warto rozważyć listy generyczne które zmniejszą objętość w pamięci o parę bajtów zachowując spory komfort użytkowania.

Jeżeli szukasz maksymalnej wydajności przy minimalnej zajętości to dynamiczny array, jeżeli minimalnej zajętości bez przejmowania się wydajnością, string. Ponieważ zapewne dla Ciebie dane z informacją o nowej linii byłyby tak samo bezsensowne jak te bez, to string jest dobrym wyjściem, bo na zewnątrz wyplujesz to czego chcą a niech oni się potem martwią jak to chcą interpretować.

nie mówiąc już o tym, że w 32-bitowych aplikacjach tworzy pliki nie można zaadresować więcej niż 2GB pamięci jednocześnie

By się postarać to by się dało. Ale generalnie to i tak limit jest z kosmosu dla programów 32bitowych.

0

Nie popieram marnowania czasu na robienie własnych formatów od plików konfiguracyjncych. Lepiej użyć yaml skoro nie lubisz xml.
Z pliku konfiguracyjnego:

# YAML
hero:
  hp: 34
  sp: 8
  level: 4
orc:
  hp: 12
  sp: 0
  level: 2

Np w pythonie możesz uzyskać strukturę słownikową, co przecież jest drzewem.

{'hero': {'hp': 34, 'sp': 8, 'level': 4}, 'orc': {'hp': 12, 'sp': 0, 'level': 2}}
0
vpiotr napisał(a)
  1. Wykorzystaj AnsiString. "Wieloliniowość" to tylko sposób interpretacji. Równie dobrze można tekst wieloliniowy traktować jako jedną wartość i parsować znaki końca linii razem z innymi albo w ogóle wrzucać je na żywca do kontrolek (od biedy).

Owszem, można wpakować wszystko do jednej linii, oddzielić jakimś separatorem (np. |) a przy zwracaniu odpowiednio dzielić np. funkcją ExtractStrings i po problemie;

vpiotr napisał(a)
  1. Po co nowy format konfiguracji kiedy co najmniej dwa UNIWERSALNE są już na tym świecie od kilku lat? (XML & JSON Corp.)

Dla zabawy, zabicia czasu, z ciekawości - nie jako komercyjne cacko czy następcę XML :]

szopenfx napisał(a)

to tylko komentarz wypisać i zapomnieć, nie będziesz chyba sortował w nim linii, edytował itp.

Dokładnie - przechować i nic więcej; żadnych nie wiedomo jakich operacji; Co do rozmiaru - nie sądzę, by było potrzebne więcej niż ShortString;

4hswe4 napisał(a)
  1. Czy musisz parsować to na linie? Jeżeli nie to właśnie czysty string może być najlepszy. Pamiętaj wtedy o niższej wydajności ewentualnych manipulacji.

Parsowanie linia po linii jest wygodne, a struktura pliku zapisana w ten sposób jest przejrzysta, więc jest to jak dla mnie wygodne i wydajne rozwiązanie;

4hswe4 napisał(a)

Jeżeli szukasz maksymalnej wydajności przy minimalnej zajętości to dynamiczny array, jeżeli minimalnej zajętości bez przejmowania się wydajnością, string. Ponieważ zapewne dla Ciebie dane z informacją o nowej linii byłyby tak samo bezsensowne jak te bez, to string jest dobrym wyjściem, bo na zewnątrz wyplujesz to czego chcą a niech oni się potem martwią jak to chcą interpretować.

Szukam czegoś, co będzie wydajne i nie będzie zbyt wiele pamięci pożerać; Przerobienie kodu na obsługę macierzy dynamicznych raczej nie wchodzi w grę, bo właściwości klasy trzeba by przerobić i ich prostota ucieknie, więc one raczej odpadają; Lista TStringList jest najwygodniejsza, ale nie potrzebuję aż tak sporej klasy do jedynie przechowania kilku krótkich łańcuchów; Czyli najprostszy łańcuch pozostaje kompromisem - i wydajny i mało pamięci zajmuje, a wieloliniową wartość można oddzielić specjalnym znakiem separatora i ew. zwrócić metodą jako lista;

To w sumie wszystko już wiem, dziękuję za odpowiedzi.

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