如何以比Java标准序列化更高的性能将对象序列化和反序列化为二进制格式
Java序列化是一种流行的机制,可以序列化和反序列化复杂的对象图;例如,对象A可以包含对对象B的引用,而对象B又包含对对象A的引用。问题是这种丰富的功能是以性能为代价的。但是,如果您不需要序列化这些类型的递归图,您可以使用一个名为Chronicle Wire的开源解决方案。它降低了复杂性,并使用树状结构,这使得它非常有效。此外,它可以支持许多不同的格式,而无需更改您的代码。本文涵盖了序列化的基础知识,并讨论了Chronicle Wire的一些主要优势。
序列化和反序列化
序列化就是把java对象编码成字节,比如我们有一个对象。假设我们的对象保存我们的应用程序状态,如果我们关闭我们的应用程序,我们将丢失状态,我们希望首先将应用程序的状态存储到磁盘,因此我们序列化我们的java状态对象。这将把对象转换成字节,可以很容易地存储。同样,如果我们想通过网络发送存储在java对象中的数据,我们首先必须序列化该对象,然后才能将其写入TCP/IP缓冲区。反序列化与序列化相反,序列化从一个字节开始,然后重新创建一个对象实例。
关于纪事报
纪事报是一个开源库,最初是为了支持历史队列和历史映射而编写的。但是,该库在任何使用序列化的代码中都很有用。Chronicle Wire与原生Java序列化的不同之处在于,它实际上支持许多不同的格式,例如二进制、YAML、JSON、原始二进制数据和CSV。Chronicle Wire背后的真正创新是,您不必更改代码来更改编码。该库将序列化的实现抽象为可插拔的连接实现。其思想是,您的对象只需要描述要序列化的内容,而不是应该如何序列化。这是由实现Marshallable接口的对象(要序列化的POJOs)来完成的。“net . open hft . chronicle . wire . marshalable”(使用Java序列化时,在“java.io.Serializable”上添加标记接口。)
编码
让我们稍微研究一下编码。我们已经提到过,Java序列化是将对象编码成二进制格式,而Chronicle Wire也可以编码成许多不同的格式。编码会影响用于存储数据的字节数,格式越紧凑,使用的字节就越少。Chronicle Wire平衡了格式的紧凑性,但没有过度压缩数据,这将占用宝贵的CPU时间,Chronicle Wire的目标是灵活和向后兼容,但也非常高性能。在不牺牲性能的情况下,以尽可能少的字节存储数据,例如,可以使用停止位编码.
一些编码更具性能,也许通过不对字段名进行编码来减少编码数据的大小,这可以通过使用Chronicle Wire的无字段二进制来实现。然而,这是一种权衡,有时牺牲一点性能并添加字段名会更好,因为这将给我们向前和向后的兼容性。
不同格式
Chronicle Wire有多种实现,每种实现在不同的场景中都很有用。例如,当我们想要提供应用程序配置文件或创建数据驱动的测试时,我们经常想要将对象序列化或反序列化为人类可读的格式,如YAML、JSON。此外,能够发送序列化为类型化JSON的java对象允许我们从应用程序的JavaScript UI层发送和接收消息。
有时,能够在编码格式之间进行互操作是很重要的。一个例子是开源产品历史队列。Chronicle Queue使用Chronicle Wire的压缩二进制格式存储其数据。然后,它读取二进制数据,并随后以人类可读的YAML格式将其记录到控制台。这对于调试或合规性报告非常有用。
示例人类可读格式
让我们看一个例子,Chronicle Wire将数据编码成简单的人类可读格式。我们使用下面的DTO:
看见WireExamples1.java
package net.openhft.chronicle.wire;
import net.openhft.chronicle.core.pool.ClassAliasPool;
import static net.openhft.chronicle.bytes.Bytes.allocateElasticOnHeap;
public class WireExamples {
public static class Car implements Marshallable {
private int number;
private String driver;
public Car(String driver, int number) {
this.driver = driver;
this.number = number;
}
}
public static void main(String...args) {
// allows the the YAML to refer to car, rather than net.openhft.chronicle.wire.WireExamples$Car
ClassAliasPool.CLASS_ALIASES.addAlias(Car.class);
Wire wire = new YamlWire(allocateElasticOnHeap());
wire.getValueOut().object(new Car("Lewis Hamilton", 44));
System.out.println(wire);
}
}
如果我们运行这段代码,它将输出以下YAML:
!Car {
number: 44,
driver: Lewis Hamilton
}
但是,如果我们只是将YmalWire从:
Wire wire = new YamlWire(allocateElasticOnHeap());
至JSON Wire:
Wire wire = new JSONWire(allocateElasticOnHeap());
然后,它将输出以下JSON:
{"number":44,"driver":"Lewis Hamilton"}
如果我们希望JSON也包含Java类型,那么我们也可以添加设置useTypes(true)
Wire wire = new JSONWire(allocateElasticOnHeap()).useTypes(true);
这也将编码java类型Car:
{"@Car":{"number":44,"driver":"Lewis Hamilton"}}
压缩二进制格式示例
让我们继续一个例子,在这个例子中,我们使用紧凑的二进制格式:
看见WireExamples1.java
package net.openhft.chronicle.wire;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.core.pool.ClassAliasPool;
public class WireExamples {
public static class Car implements Marshallable {
private int number;
private String driver;
public Car(String driver, int number) {
this.driver = driver;
this.number = number;
}
}
public static void main(String...args) {
ClassAliasPool.CLASS_ALIASES.addAlias(Car.class);
Wire wire = WireType.FIELDLESS_BINARY.apply(Bytes.allocateElasticOnHeap());
wire.getValueOut().object(new Car("Lewis Hamilton", 44));
System.out.println(wire.bytes().toHexString());
}
}
它输出以下内容:
00000000 B6 03 43 61 72 82 10 00 00 00 2c ee 4c 65 77 69 Car,Lewi
00000010 73 20 48 61 6d 69 6c 74 6f 6e s Hamilton
反序列化示例
到目前为止,所有示例都涵盖了序列化,因此当涉及到反序列化时,我们可以从数据开始,例如:
{"@Car":{"number":44,"driver":"Lewis Hamilton"}}
然后我们可以将这个JSON反序列化回一个JAVA对象:
package net.openhft.chronicle.wire;
import static net.openhft.chronicle.core.pool.ClassAliasPool.CLASS_ALIASES;
public class WireExamples {
public static class Car implements Marshallable {
private int number;
private String driver;
public Car(String driver, int number) {
this.driver = driver;
this.number = number;
}
}
public static void main(String...args) {
CLASS_ALIASES.addAlias(Car.class);
final Wire wire = new JSONWire().useTypes(true);
wire.bytes().append("{"@Car":{"number":44,"driver":"Lewis Hamilton"}}");
final Object object = wire.getValueIn().object();
}
}
向前和向后兼容性示例
如果字段名是编码的,如果我们更改DTO,以包含“int numberOfPitStops”(见下面的示例),当反序列化发生时,这些数值将默认为零,它知道的字段将照常加载。
package net.openhft.chronicle.wire;
import static net.openhft.chronicle.core.pool.ClassAliasPool.CLASS_ALIASES;
public class WireExamples {
public static class Car implements Marshallable {
private int number;
private int numberOfPitStops;
private String driver;
public Car(String driver, int number) {
this.driver = driver;
this.number = number;
}
}
public static void main(String...args) {
CLASS_ALIASES.addAlias(Car.class);
final Wire wire = new JSONWire().useTypes(true);
wire.bytes().append("{"@Car":{"number":44,"driver":"Lewis Hamilton"}}");
final Object object = wire.getValueIn().object();
}
}
示例编码字符串
通常使用UTF8标准对字符串进行编码,但是,也可以使用基本编码器对字符串进行编码,例如Base64编码器,它可以将数据存储到更紧凑的字符串或基元字段中。对于每个字节,有256种不同的组合(这是因为一个字节由8位组成,位为0或1,给出2^8组合,因此有256种),然而,如果我们选择使用基本编码器,并假设我们可以将我们的字符串限制为以下字符“.
abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz 0123456789+”[这是一些最常用的字符],那么我们可以使用这个基本编码器来存储上述这些字符中的10个
当然,您可以创建自己的基本编码,它不一定要包含这么多字符。使用更少的字符,您可以受益于更大的紧凑性。如前所述,数据越紧凑,读写速度就越快。
下面是Chronicle Wire如何以长整型存储小字符串的示例,YAML序列化程序显示了字符串表示形式,但字符串仅使用8字节长存储在对象中,同样,二进制序列化程序将使用更紧凑的8字节长表示形式。
看见WireExamples2.java
package net.openhft.chronicle.wire;
import net.openhft.chronicle.bytes.Bytes;
import static net.openhft.chronicle.core.pool.ClassAliasPool.CLASS_ALIASES;
public class WireExamples2 {
public static class TextObject extends SelfDescribingMarshallable {
transient StringBuilder temp = new StringBuilder();
@LongConversion(Base64LongConverter.class)
private long text;
public TextObject(CharSequence text) {
this.text = Base64LongConverter.INSTANCE.parse(text);
}
public CharSequence text() {
Base64LongConverter.INSTANCE.append(temp, text);
return temp;
}
}
public static void main(String...args) {
CLASS_ALIASES.addAlias(TextObject.class);
final Wire wire = new BinaryWire(Bytes.allocateElasticOnHeap());
// serialize
wire.getValueOut().object(new TextObject("SAMPLETEXT"));
// log out the encoded data
System.out.println("encoded to=" + wire.bytes().toHexString());
// deserialize
System.out.println("deserialized=" + wire.getValueIn().object());
}
}
public Car(String driver, int number) {
this.driver = driver;
this.number = number;
}
}
public static void main(String...args) {
CLASS_ALIASES.addAlias(Car.class);
final Wire wire = new JSONWire().useTypes(true);
wire.bytes().append("{"@Car":{"number":44,"driver":"Lewis Hamilton"}}");
final Object object = wire.getValueIn().object();
}
}
package net.openhft.chronicle.wire;
import net.openhft.chronicle.bytes.Bytes;
import static net.openhft.chronicle.core.pool.ClassAliasPool.CLASS_ALIASES;
public class WireExamples2 {
public static class TextObject extends SelfDescribingMarshallable {
transient StringBuilder temp = new StringBuilder();
@LongConversion(Base64LongConverter.class)
private long text;
public TextObject(CharSequence text) {
this.text = Base64LongConverter.INSTANCE.parse(text);
}
public CharSequence text() {
Base64LongConverter.INSTANCE.append(temp, text);
return temp;
}
}
public static void main(String...args) {
CLASS_ALIASES.addAlias(TextObject.class);
final Wire wire = new BinaryWire(Bytes.allocateElasticOnHeap());
// serialize
wire.getValueOut().object(new TextObject("SAMPLETEXT"));
// log out the encoded data
System.out.println("encoded to=" + wire.bytes().toHexString());
// deserialize
System.out.println("deserialized=" + wire.getValueIn().object());
}
}
结论
Chronicle Wire允许您将对象序列化和反序列化为二进制格式,也可以同时转换为许多不同的格式,因为它比Java标准序列化具有更高的性能。
最后
感谢阅读,点赞收藏+关注!更多的java课程学习资料博主已经整理好了,有兴趣学习的朋友可以点击获取免费学习资料!!