Transformation von Datenstrukturen für eine transportgerechte Verpackung

Bestimmend für den Aufbau einer Datenstruktur ist nicht der Transport sondern die Verwendung

Hinweis: Diese Programmiertechnik war vor JSON da. Also sinngemäß wie: Hier wohnte Goethe nicht!

Templating Engines wie z.B. HTML::Template erfreuen sich einer ganz bestimmten Datenstruktur damit die darin enthaltenen Daten in ein Template gerendert werden können. Insbesondere betrifft das zyklischen Datenstrukturen die Daten zur Darstellung von Listen und Tabellen beeinhalten (Loops in Templates). Beispielsweise eine Tabelle mit den beiden Spalten url und cnt, hierzu müssen die Daten in folgender Struktur vorliegen:

[
    { url => '/index.html', cnt => 3212},
    { url => '/map.html', cnt => 23}
];

Und so weiter. Die Struktur ist also ein Array mit Objekten und als JSON transportiert ist das auch optisch sichtbar. In Fakt jedoch schleppt diese Struktur eine ganze Menge redundante Daten mit sich rum, nämlich die Feldnamen! Die zu übertragende Datenmenge wäre nur halb so groß, wenn die Feldnamen nur einmal übertragen würden, sozusagen als Überschriften für die darzustellende Tabelle. Eine dem entsprechende Datenstruktur sähe dann so aus:

[
    'url',
    'cnt',
    '/index.html',
    '3212',
    '/map.html',
    '23'
];

Womit der Informationsgehalt genau derselbe wäre, einen Datenverlust gäbe es nicht. Zum Transportieren nun, könnten wir sämtliche Arrayelemente auf eine einheitliche Länge bringen:

[
    'url           ',
    'cnt           ',
    '/index.html   ',
    '3212          ',
    '/map.html     ',
    '23            '
];

Indem zum Beispiel mit Leerzeichen oder Nullbytes aufgefüllt wird. Ein solches Array als Datei auf die Reise zu schicken ist dann in Hinblick auf die Wiederherstellung der Daten denkbar einfach. Serverseitiger PerlCode untenstehend:

# Einfache Abfrage der Top 30
my $q   = q(
    SELECT url, count(url) as cnt
    FROM log group by url
    order by cnt desc limit 30
);

# Zum Template passende Datenstruktur
my $slice = $dbh->selectall_arrayref($q, { Slice => {}});

# Übertragung vorbereiten
# Namen der Felder bzw. Spalten
my @cols = qw(url cnt);

# Zum Transportieren genügt ein Array
my @transport = map{ $_->{url}, $_->{cnt} } @$slice;

# Spaltennamen am Anfang einfügen
unshift @transport, @cols;

# Alle Elemente auf eine einheitliche Länge bringen
# Auffüllen mit Leerzeichen auf 80 Zeichen je Feld
@transport = map{ pack "A80", $_ }@transport;
print join "", @transport;

Und natürlich darf auch der Code zur Wiederherstellung der Daten nicht fehlen, untenstehend ist er in JavaScript. Die wiederhergestellte Datenstruktur ist ein Array mit Objekten passend zu einer Templating Engine in JavaScript die ganz ähnlich wie HTML::Template funktioniert und vom Aufbau her dieselben Templates verwendet:

// Die Response wird als ArrayBuffer angefordert
xhr.responseType = 'arraybuffer';
xhr.onload = function(){
    // Array für die Objekte
    var stats = [];
    var offs = 0;
    var dv = new DataView(this.response);
    while(offs < this.response.byteLength){
        var url = dv.getString(offs, 80);
        offs += 80;
        var cnt = dv.getString(offs, 80);
        offs += 80;

        // trim() entfernt die angehängten Leerzeichen
        // Objekte dem Array hinzufügen
        stats.push({
            url:url.trim(),
            cnt:cnt.trim()
        });
    }

    // Daten in das Template rendern
    var templ = $('#templ').html();
    $('#out').html( xr(templ, {stats:stats})  )
};

Arrayelemente in Feldern mit variabler Länge

Beim Transport von Arrayelementen in Feldern mit fester Länge ergeben sich 2 Nachteile:

Das Problem ist ganz einfach lösbar, indem die Länge des Feldes dem jeweiligen Feld selbst vorangestellt wird, Code in Perl:

    # Zum Transportieren genügt ein Array
    # Jedem Element wird die Länge in Bytes vorangestellt
    # Auf gültige Werte wird ebenfalls gleich mit geprüft
    my @transport = map{
        pack("N", length( $_->{url} ? $_->{url} : '')), $_->{url} ? $_->{url} : '',
        pack("N", length( $_->{cnt} ? $_->{cnt} : '')), $_->{cnt} ? $_->{cnt} : ''
    } @{$self->{STASH}{slice}};
    $self->{CONTENT} = join "", @transport;

Wobei diese Längenangabe mit einem Datentyp 32 Bit Integer in die Sequenz geschrieben wird. Zur Wiederherstellung sind also zyklisch immer genau 4 Bytes zu lesen, untenstehender JavaScrip Code implementiert diesen einfachen Algorithmus:

xhr.responseType = "arraybuffer";
xhr.onload = function(){
    var dv = new DataView(this.response);
    if(this.status != 200) {
        pretext( dv.getString(0) );
    }
    else{
        var offs = 0;
        while( offs < dv.byteLength ){
            var len = dv.getUint32(offs);
            offs += 4;
            var el = dv.getString(offs, len);
            offs += len;
            console.log(len, el);
        }
    }
};

Die Fehlerbehandlung erfolgt in diesem Fall über den HTTP Status der Response.


Datenschutzerklärung: Diese Seite dient rein privaten Zwecken. Auf den für diese Domäne installierten Seiten werden grundsätzlich keine personenbezogenen Daten erhoben. Das Loggen der Zugriffe mit Ihrer Remote Adresse erfolgt beim Provider soweit das technisch erforderlich ist. s​os­@rolf­rost.de.