Take: Daten mit Storable speichern

Moin,

ich verwende das Modul Storable um Daten meines Scripts zu speichern und beim nächsten Aufruf wieder zur Verfügung zu haben. Dafür verwende ich folgendes zum Schreiben:

  
@foo = ("foo", "bar", "foo1", "bar1");  
@bar = ("foo2", "bar2", "foo3", "bar3");  
  
%complete = (foo => \@foo, bar => \@bar);  
  
open (DB, ">db.db");  
store_fd(\%complete, *DB);  
close (DB);  

Und zum Einlesen:

  
open (DB, "<db.db");  
%complete = %{fd_retrieve(*DB)};  
close (DB);  
  
@foo = %{$complete{"foo"}};  
@bar = %{complete{"bar"}};  

Das wird bei vielen Variablen recht unübersichtlich (vorallem beim dereferenzieren nach dem Einlesen). Über Vorschläge zur Übersichtlichkeitsoptimierung würde ich mich daher freuen! :D

Gruß,
Take

  1. ich verwende das Modul Storable um Daten meines Scripts zu speichern und beim nächsten Aufruf wieder zur Verfügung zu haben. Dafür verwende ich folgendes zum Schreiben:

    @foo = ("foo", "bar", "foo1", "bar1");
    @bar = ("foo2", "bar2", "foo3", "bar3");

    %complete = (foo => @foo, bar => @bar);

    open (DB, ">db.db");
    store_fd(%complete, *DB);
    close (DB);

    
    >   
    > Und zum Einlesen:  
    >   
    > ~~~perl
      
    
    > open (DB, "<db.db");  
    > %complete = %{fd_retrieve(*DB)};  
    > close (DB);  
    >   
    > @foo = %{$complete{"foo"}};  
    > @bar = %{complete{"bar"}};  
    > 
    
    

    Das wird bei vielen Variablen recht unübersichtlich (vorallem beim dereferenzieren nach dem Einlesen). Über Vorschläge zur Übersichtlichkeitsoptimierung würde ich mich daher freuen! :D

    Deine Frage hat also nichts mit Storable an sich zu tun, sondern mit dem Hash und der Frage wie du ihn strukturierst.

    Aber etwas möchte ich dir doch ans Herzen legen. Unter Umständen solltest du lock_store und lock_retrieve verwenden. Sonst hast du ein Schlamassel.

    Ich finde die Datenstruktur selbst unproblematisch. Man muss sie halt Dokumentieren, so wie du auch ein anderes Datenformat dokumentieren würdest.

    Eventuell hilft es, wenn man nicht alle Daten im gleichen hash speichert, sondern mehrere Hashes separat über Storable verwaltet.
    Das musst du aber selber entscheiden.

    mfg Beat

    --
    ><o(((°>           ><o(((°>
       <°)))o><                     ><o(((°>o
    Der Valigator leibt diese Fische
    1. hi,

      .. Über Vorschläge zur Übersichtlichkeitsoptimierung würde ich mich daher freuen! :D

      Deine Frage hat also nichts mit Storable an sich zu tun, sondern mit dem Hash und der Frage wie du ihn strukturierst.

      Genau.

        
      # Arrays  
      my $href = {  
        foo => [1,2,3],  
        bar => [4,5,6],  
      };  
        
      # 'Objekte'  
      my $href = {  
        foo => {  
          name => 'Otto',  
          vname => 'Hannes',  
        },  
        bar => {  
          name => 'Haselhuhn',  
          vname => 'Horst,  
          plz => 55276,  
        },  
      };  
      
      

      Hotti

      --
      Sind die Hühner platt wie Teller,
      war der Trecker wieder schneller.
  2. @foo = ("foo", "bar", "foo1", "bar1");
    @bar = ("foo2", "bar2", "foo3", "bar3");

    %complete = (foo => @foo, bar => @bar);

    open (DB, ">db.db");
    store_fd(%complete, *DB);
    close (DB);

    
    >   
    > Und zum Einlesen:  
    >   
    > ~~~perl
      
    
    > open (DB, "<db.db");  
    > %complete = %{fd_retrieve(*DB)};  
    > close (DB);  
    >   
    > @foo = %{$complete{"foo"}};  
    > @bar = %{complete{"bar"}};  
    > 
    
    

    was mir auffällt (neben dem was Beat schon bemängelt):
    du verwendest kein use strict?
    Warum benutzt du Arrays, wenn du ein Hash hast?
    Warum prüfst du nicht die Dateioperationen?

    Das wird bei vielen Variablen recht unübersichtlich (vorallem beim dereferenzieren nach dem Einlesen). Über Vorschläge zur Übersichtlichkeitsoptimierung würde ich mich daher freuen! :D

    Verwende einen eigenen Namespace zum einlesen und speichern der Daten.

    Die get Funktion könnte dann z.b. so aussehen:

    sub get{  
    my $name = shift;  
    my $ref = $complete{$name};  
    return unless $ref;  
    return wantarray ? %$ref : $ref;  
    }  
    
    

    Struppi.

    1. Moin,

      @foo = ("foo", "bar", "foo1", "bar1");
      @bar = ("foo2", "bar2", "foo3", "bar3");

      %complete = (foo => @foo, bar => @bar);

      open (DB, ">db.db");
      store_fd(%complete, *DB);
      close (DB);

      
      > >   
      > > Und zum Einlesen:  
      > >   
      > > ~~~perl
        
      
      > > open (DB, "<db.db");  
      > > %complete = %{fd_retrieve(*DB)};  
      > > close (DB);  
      > >   
      > > @foo = %{$complete{"foo"}};  
      > > @bar = %{complete{"bar"}};  
      > > 
      
      

      was mir auffällt (neben dem was Beat schon bemängelt):
      du verwendest kein use strict?

      das ist nur ein Beispiel. In Wirklichkeit benutze ich File-Locking wie hier beschrieben

      Warum benutzt du Arrays, wenn du ein Hash hast?

      Sorry, mein Fehler. Es muss natürlich:

      @bar = @{$complete{"bar"}}; heißen.

      Warum prüfst du nicht die Dateioperationen?

      Du meinst mit

      open (FOO, "bar.txt") || die "someerror";? -> s.o.

      Das wird bei vielen Variablen recht unübersichtlich (vorallem beim dereferenzieren nach dem Einlesen). Über Vorschläge zur Übersichtlichkeitsoptimierung würde ich mich daher freuen! :D

      Verwende einen eigenen Namespace zum einlesen und speichern der Daten.

      Die get Funktion könnte dann z.b. so aussehen:

      sub get{

      my $name = shift;
      my $ref = $complete{$name};
      return unless $ref;
      return wantarray ? %$ref : $ref;
      }

      
      >   
        
      Damit muss ich trotzdem jede Variable einzeln behandeln:  
        
      ~~~perl
        
      @foo = get("foo");  
      @bar = get("bar");  
      ...  
      
      

      -> Das Problem bleibt.
      Ich hatte an eine Art Schleife gedacht, die mir für jeden Key im Hash %complete das im dazugehörigen Value referenzierte Array in das Array mit dem Namen des Keys dereferenziert. (Was ein Satz...) :D
      Also (Pseudocode):

      foreach (keys(%complete)) {  
          @array_dessen_name_in_$_steht = @{$complete{$_}};  
      }  
      
      

      Struppi.

      Trotzdem vielen Dank!
      Gruß,
      Take

      1. @array_dessen_name_in_$_steht = @{$complete{$_}};

        Huuuh, symbolischer Zugriff, wie gruselig. Das ist eine schlechte Praxis, ich rate ab.

        Beat hat dir schon geraten, den die Datenstruktur bzw. den Zugriff darauf zu überdenken. Du solltest einfach eine Hashref benutzen, siehe Seite 55 in Modern Perl.

          
            $complete = {  
                foo => [qw(foo bar foo1 bar1)],  
                bar => [qw(foo2 bar2 foo3 bar3)],  
            };  
        
        

        Dort, wo du früher @foo geschrieben hast, schreibst du stattdessen @{ $complete->{foo} }, analog @bar zu @{ $complete->{bar} }. Du kannst über die Namen sehr einfach iterieren.

          
            frob_array(@{ $complete->{$_} }) for qw(foo bar);  
        
        

        Pro-Tipp zum Schluss: immer die *n*store-Variante benutzen, siehe http://p3rl.org/Perl::Critic::Policy::Storable::ProhibitStoreOrFreeze

  3. Moin Moin!

    Noch ein kleiner Nachschlag: Storable ist kein zeitlich stabiles Format, d.h. eine neue Storable-Version kann ein neues Format haben, das zum alten Format inkompatibel ist. Das ist in der Vergangenheit mehrfach passiert. Siehe FORWARD COMPATIBILITY und Changelog.

    Dein Beispiel hat konstant große Arrays, die jeweils einfache Strings enthalten. Das läßt sich auch mit SQLite gut abbilden, einfach als eine Tabelle, die den Hash-Key als Primary Key nutzt und jedes Array-Element als eine weitere Spalte in der Tabelle abbildet.

    Ganz nebenbei spart Dir SQLite (DBD::SQLite) das Theater mit Locking und File-Handling komplett, und du mußt nicht immer alle Daten in den Speicher ziehen, sondern nur die, die Du wirklich brauchst.

    Natürlich ist der Overhead einer kompletten SQL-Engine größer als der, "einfach nur" ein paar Perl-interne Strukturen in eine Datei zu werfen und wieder heraus zu pulen. Dafür ist das Datenformat von SQLite aber beständiger, ändert sich AFAIK nur mit Major Relases (1.x => 2.x, 2.x => 3.x).

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
  4. Moin,

    ich verwende das Modul Storable um Daten meines Scripts zu speichern und beim nächsten Aufruf wieder zur Verfügung zu haben.

    Falls Du nur Arrays hast, die lassen sich recht einfach in Raw-Dateien serialisieren. Zwei Varianten:

    1 Alle Array-Elemente haben ungefähr dieselbe Länge. Nach dem Längsten richtet sich die Schablone für pack(), entweder "A99" oder "Z99", wobei die 99 hier für die Länge eines Records steht (anpassen). Schablone "A" füllt die bytes mit Leerzeichen auf, "Z" mit binären Null-bytes, dabei ist eine NULL für das Stringende reserviert. Das Dateihandle wird in den binmode geschaltet und das Array ohne Zeilenumbrüche in die Datei geschrieben, genauso wird es da auch wieder ausgelesen. flock() ist auf das Handle anzuwenden, kein Thema.

    2 Die Array-Elemente haben sehr unterschiedliche Längen. Da wird beim Serialisieren die Länge mit length() ermittelt und als 4-byte-Integer mitgespeichert, das sieht dann so aus:

    |_|_|_|_|<----- Listenelement vaiable Länge --->|_|_|_|_|<----- Listenelement vaiable Länge --->|_ usw.

    |_|_|_|_| <- diese 4 byte beeinhalten die Länge eines Array-Elements, pack("N", length($e)); erzeugt diese 4 byte.

    Das Auslesen (deserialize) geht genau andersherum, 4 byte lesen, die Länge ergibt sich mit unpack("N", $4byte) und so die Anzahl der bytes für das nächste Listenelement.

    Wenn Du es ganz sicher machen willst, schreibe an den Anfang oder an das Ende der Datei eine Checksumme, dafür gibt es wiederum mehrere Möglichkeiten.

    Weitere Möglichkeiten ergeben sich mit tie(), der Zugriff auf die Datei ist dann ein Zugriff auf ein Array.

    Für Hashes einen eigenen Algorithmus zu finden ist auch nicht schwer, es ist nur so, dass darüber kaum was im Internet zu finden ist. Es lohnt jedoch eine Auseinandersetzung mit dem Thema allemal, in meiner Praxis verwende ich so eigene Module für Serialize/Unserialize, die sehr performant arbeiten, dessen Einsatz ich stets gegenüber einer DB-Lösung abwäge und meistens auch bevorzuge.

    Hotti