Binäres JSON mit bson4jackson
JSON hat sich inzwischen als hervorragende Alternative zu XML etabliert. Die meisten JSON-Parser in Java sind jedoch noch relativ langsam. Auf meiner Suche nach schnelleren Libraries bin ich auf zwei Dinge gestoßen: BSON und Jackson.
BSON ist binär kodiertes JSON. Das Format ist besonders darauf ausgelegt, schnell und einfach maschinell lesbar zu sein. BSON wurde bekannt durch seine Verwendung als primäres Austauschformat für das dokumentenorientierte Datenbankmanagementsystem MongoDB. Jackson ist laut dem JVM Serializers Benchmark einer der schnellsten JSON-Prozessoren, die es gibt. Außerdem bietet Jackson die Möglichkeit, eigene Erweiterungen hinzuzufügen und somit die Bibliothek um weitere Austauschformate zu ergänzen.
bson4jackson
Das ist der Punkt, an dem bson4jackson ins Spiel kommt. Die Bibliothek erweitert Jackson um die Möglichkeit, BSON-Dokumente zu lesen und zu schreiben. Dadurch, dass bson4jackson vollständig integriert ist, kann man die wirklich sehr gelungene API von Jackson nutzen. Somit lassen sich einfache POJOs sehr schnell serialisieren. Angenommen man möchte Objekte der folgende Klasse mittels BSON austauschen:
public class Person {
private String _name;
public void setName(String name) {
_name = name;
}
public String getName() {
return _name;
}
}
Dann kann man den so genannten ObjectMapper benutzen, der das schnelle
Serialisieren von POJOs erlaubt:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.undercouch.bson4jackson.BsonFactory;
public class ObjectMapperSample {
public static void main(String[] args) throws Exception {
//create dummy POJO
Person bob = new Person();
bob.setName("Bob");
//serialize data
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectMapper mapper = new ObjectMapper(new BsonFactory());
mapper.writeValue(baos, bob);
//deserialize data
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Person clone_of_bob = mapper.readValue(bais, Person.class);
assert bob.getName().equals(clone_of_bob.getName());
}
}
Oder man verwendet die Streaming API von Jackson und serialisiert das Objekt manuell:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import de.undercouch.bson4jackson.BsonFactory;
public class ManualSample {
public static void main(String[] args) throws Exception {
//create dummy POJO
Person bob = new Person();
bob.setName("Bob");
//create factory
BsonFactory factory = new BsonFactory();
//serialize data
ByteArrayOutputStream baos = new ByteArrayOutputStream();
JsonGenerator gen = factory.createJsonGenerator(baos);
gen.writeStartObject();
gen.writeFieldName("name");
gen.writeString(bob.getName());
gen.close();
//deserialize data
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
JsonParser parser = factory.createJsonParser(bais);
Person clone_of_bob = new Person();
parser.nextToken();
while (parser.nextToken() != JsonToken.END_OBJECT) {
String fieldname = parser.getCurrentName();
parser.nextToken();
if ("name".equals(fieldname)) {
clone_of_bob.setName(parser.getText());
}
}
assert bob.getName().equals(clone_of_bob.getName());
}
}
Optimierung fürs Streaming
Eine Einschränkung des BSON-Formats ist, dass jedes Dokument mit einer
Längenangabe beginnt. Beim Schreiben muss die Länge des gesamten Dokuments
also im Voraus bekannt sein. bson4jackson ist gezwungen, das Dokument
zu puffern bevor es in den OutputStream geschrieben werden kann. Der
Parser von bson4jackson ignoriert die Längeangabe jedoch, sodass man
sie auch weglassen kann. Dazu muss man die BsonFactory folgendermaßen erzeugen:
BsonFactory fac = new BsonFactory();
fac.enable(BsonGenerator.Feature.ENABLE_STREAMING);
Dies kann den Serialisierungsvorgang für große Dokumente sehr beschleunigen und den Speicherverbrauch stark reduzieren. Der offizielle MongoDB-Treiber für Java ignoriert die Längenangabe übrigens auch. Die Optimierung lässt sich also ebenfalls nutzen, wenn die von bson4jackson geschriebenen Dokumente mit dem MongoDB-Treiber gelesen werden sollen.
Performance
In der Version 1.1.0 von bson4jackson wurden neben der Unterstützung für
Jackson 1.7 auch einige Verbesserungen hinsichtlich der Performance
vorgenommen. Inzwischen ist bson4jackson deutlich schneller als der
offizielle MongoDB-Treiber für Java (Stand: Januar 2011). Dies trifft
beim Serialisieren jedoch nur zu, wenn die Streaming-API verwendet wird,
da durch den ObjectMapper von Jackson noch ein kleiner Overhead dazu
addiert werden muss (der MongoDB-Treiber arbeitet allerdings auch mit
einer Art Streaming-API). Beim Deserialisieren ist bson4jackson immer
schneller. Die aktuellen Ergebnisse des Benchmarks kann man unter
folgender Seite nachschauen:
https://github.com/eishay/jvm-serializers/wiki
Kompatibilität mit MongoDB
In Version 1.2.0 wurde die Kompatibilität von bson4jackson zu MongoDB
deutlich verbessert. Dank der Beteiligung von James Roper
unterstützt die BsonParser-Klasse nun das Feature HONOR_DOCUMENT_LENGTH.
Es bringt den Parser dazu, die ersten 4 Bytes, also die Größe
eines Dokuments, auszuwerten und zu beachten. Natürlich funktioniert dies nur,
wenn das Feature BsonGenerator.Feature.ENABLE_STREAMING nicht während
der Erzeugung des Dokuments aktiviert war.
Das Feature ist besonders nützlich, um mehrere Dokumente aus einem Input-Stream von MongoDB zu lesen. Es kann wie folgt aktiviert werden:
BsonFactory fac = new BsonFactory();
fac.enable(BsonParser.Feature.HONOR_DOCUMENT_LENGTH);
BsonParser parser = (BsonParser)fac.createJsonParser(...);
Unterstützung für Jackson 2.0
Jackson 2.0 wird seit bson4jackson 2.0 unterstützt. Falls Sie nach einer Version suchen, die mit Jackson 1.x läuft, dann laden Sie bitte bson4jackson 1.3.0 herunter.
Download
Vorkompilierte Binaries
Vorkompilierte Binärdateien von bson4jackson kann man sich in meinem GitHub-Repository herunterladen. Zusätzlich benötigt man nur noch eine Kopie von Jackson und schon kann es losgehen.
Maven/sbt/buildr
Alternativ kann man auch Maven benutzen, um bson4jackson herunterzuladen:
<dependencies>
<dependency>
<groupId>de.undercouch</groupId>
<artifactId>bson4jackson</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
Für sbt muss man folgende Zeile zu seiner Projektbeschreibung hinzufügen:
val bson4jackson = "de.undercouch" % "bson4jackson" % "2.0.0"
Für buildr kann man folgenden Code nutzen:
compile.with 'de.undercouch:bson4jackson:jar:2.0.0'
Add to: