Sequenzielle Datenablage

Ziel dieser kleinen Low-Level-Bibliothek ist es, möglichst einfach Daten in einem Stream ablegen und wieder auslesen zu können.
Die Daten sollen dabei einschliesslich ihres Typs hinterlegt und wieder korrekt ausgelesen werden können.

Das Einlesen erfolgt in der gleichen Reihenfolge, wie die Daten gespeichert wurden. Dies soll ein möglichst performantes Schreiben und Lesen ermöglichen.
Ein wahlfreier Zugriff ist nicht möglich.

Das Projekt

inhaltl. Voraussetzungen

Ein grundlegendes Verständnis der Java-Datentypen sowie über die strukturierte Verarbeitung von Daten ist hilfreich.

techn. Voraussetzungen

Die aktuelle Implementierung liegt in Java vor, das Datenformat kann aber auch in anderen Sprachen gelesen und geschrieben werden.

Voraussetzungen

Was ist das Ziel?

Es sollen nahezu beliebige Daten aufeinander folgend abgelegt und später wieder ausgelesen werden können.

Was diese Daten repräsentieren und wie sie weiter verarbeitet werden, obliegt der jeweiligen Anwendung. Diese Bibliothek bietet nur die Möglichkeit, die Daten einschliesslich ihres Typs abzuspeichern und wieder auszulesen.

Konkrete Anwendungsfälle

  • Speichern von Datenreihen
  • Ablegen von Wortlisten
  • Export von einfachen Tabellendaten

Was ist nicht das Ziel?

Diese Bibliothek ist kein Ersatz für die Java Serialisierung.

Ebenso ist sie kein Ersatz für das Ablegen von Daten in JSON oder XML.

Ziel

Datentypen

Die nachfolgenden Datentypen werden beim Schreiben und Lesen unterstützt.

Datentyp Marker Längenangabe Daten
int 00000001 (keine) 4 Bytes
long 00000010 (keine) 8 Bytes
double 00000011 (keine) 8 Bytes
Date 00000100 (keine) 8 Bytes
Date (null) 00100100 (keine) (keine)
byte[] 01000101 int = 4 Bytes n Bytes
byte[] (null) 00100101 (keine) (keine)
char[] 01000110 int = 4 Bytes n*2 Bytes
char[] (null) 00100110 (keine) (keine)
String 01000111 int = 4 Bytes n*2 Bytes
String (null) 00100111 (keine) (keine)
custom byte[] 01000001 int = 4 Bytes n Bytes
custom byte[] 10000010 long = 8 Bytes n Bytes

Tipps zu den Markern

Aus den ersten beiden Bits lässt sich ablesen, ob es eine Längenangabe gibt oder ob sich um "direkte" Daten handelt.

  • 00...... direkte Daten
  • 01...... folgend 4 Bytes (int) mit der Länge
  • 10...... folgend 8 Bytes (long) mit der Länge

Bei den Datentypen, die null annehmen können, zeigt das 3. Bit (..x.....) an, ob es sich um die null-Variante handelt (die Längenbits sind 00 und die anderen Bits entsprechen denen der Nicht-null-Variante).

API

Schreiben

Zum Schreiben wird die Klasse BinaryOutput verwendet. Diese bekommt einen OutputStream übergeben, auf den dann die eigentlichen Daten geschrieben werden. Für die beschriebenen Datentypen stehen jeweils passende Methoden zum Schreiben bereit.

Abhängig vom Datentyp wird zunächst ein Marker Byte geschrieben, sowie - falls notwendig - eine Längenangabe. Danach werden die eigentlichen Daten geschrieben.

Lesen

Das Lesen erfolgt über die Klasse BinaryInput, der ein InputStream übergeben wird.

Über die Methode read() wird der nächste Wert gelesen. Die Rückgabe von null signalisiert dabei das Ende der Daten.

Wenn erfolgreich Daten gelesen werden konnten, werden diese als BinaryInputData zurückgegeben. Dieses Objekt enthält sowohl die eigentlichen Daten als auch den Datentyp, der mit den Marker Byte Werten aus MagicMarker verglichen werden kann.

Implementierung

Sonstige Anmerkungen und Tipps

Experimentell: Wenn im BinaryInput die Marker Mask gesetzt ist, werden nur die Daten der angegebenen Marker gelesen, alle anderen Daten werden mit der Stream-Methode skip(n) übersprungen. Dies ermöglicht es, spezielle Dateien schneller einzulesen oder gezielt Daten zu suchen. Die Marker Mask kann dabei jederzeit wieder geändert werden.

Da sich BinaryInput wie ein Pull-basierter Parser verhält, kann - je nach Anforderung - die Instanz an untergeordnete Klassen zur Verarbeitung von Teilbereichen übergeben werden. Das Datenformat muss dabei jedoch so gewählt werden, dass die Rücksprünge passend vorgenommen werden (ein "unread" ist nicht möglich).

Es sollten nur Daten mit BinaryInput eingelesen werden, die mit BinaryOutput (bzw. einer kompatiblen Implementierung) geschrieben wurden. Da seitens des Datenformates weder Formatprüfungen vorgenommen noch Prüfsummen verwendet werden, ist das Verhalten beim Einlesen inkompatibler Daten ungewiss.

Es wird kein allgemeiner Marker zu Beginn der Daten geschrieben (und beim Lesen auch nicht erwartet). Falls notwendig, muss ein solcher Marker vom Programm ergänzt und auch selbst ausgewertet werden.

Sonstiges

Ideen und Planungen

  • Implementierung in anderen Sprachen, z.B. C#
  • Keine direkte Verwendung von InputStream und OutputStream, um flexiblere und ggf. performantere Umsetzungen zu ermöglichen.
  • Beispiel-Code für Schreiben und Lesen von Daten.
  • You name it.

Weitere Ideen können gerne in den GitHub Issues erfasst oder das Projekt geforked und erweitert werden.

Zukünftiges

Disclaimer

Sowohl der Quellcode als auch die Informationen in diesem Text sind sorgfältig zusammengestellt und getestet worden. Trotzdem sind Fehler nicht auszuschliessen. In diesem Fall bitte gerne einen Issue auf GitHub zum Projekt erstellen. Danke.

Zusatzinfos
Navigation