不同格式的高性能Java序列化

567 阅读8分钟

如何以比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课程学习资料博主已经整理好了,有兴趣学习的朋友可以点击获取免费学习资料!!