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

Java 序列化是一种流行的机制,您可以在其中序列化和反序列化复杂的对象图;例如,对象 A 可以包含对对象 B 的引用,而对象 B 又具有对对象 A 的引用。问题在于,这种丰富的功能是有性能代价的。但是,如果您不需要序列化这些类型的递归图,则可以改用名为 Chronicle Wire 的开源解决方案。它降低了复杂性并使用树状结构,使其非常高效。此外,它可以支持许多不同的格式,而无需更改代码。本文介绍了序列化的基础知识,并讨论了 Chronicle Wire 的一些主要优点。

序列化和反序列化
序列化是关于将 java 对象编码为字节,例如,我们有一个对象。假设我们的对象保存我们的应用程序状态,如果我们要关闭我们的应用程序,我们将丢失状态,我们希望首先将应用程序的状态存储到磁盘上,因此我们序列化我们的 java 状态对象。这会将对象转换为字节,可以轻松存储。同样,如果我们想通过网络发送存储在 java 对象中的数据,我们首先必须序列化对象,然后才能将其写入 TCP/IP 缓冲区。反序列化与序列化相反,在序列化中,我们从字节开始并重新创建对象实例。

关于Chronicle Wire
Chronicle Wire 是一个开源库,最初是为了支持 Chronicle Queue 和 Chronicle Map 而编写的。但是,该库在任何使用序列化的代码中都很有用。Chronicle Wire 与原生 Java 序列化的不同之处在于它实际上支持许多不同的格式,例如二进制、YAML、JSON、原始二进制数据和 CSV。Chronicle Wire 背后的真正创新在于,您不必更改代码即可更改编码。该库将序列化的实现抽象为可插拔的 Wire 实现。这个想法是,你的对象只需要描述要序列化的内容,而不需要描述它应该如何序列化。这是由实现 Marshallable 接口的对象(要序列化的 POJO)完成的。“net.openhft.chronicle.wire.Marshallable” (使用 Java 序列化时,在 “java.io.Serializable” 上添加标记接口。

编码
让我们深入研究一下编码。我们已经提到过,Java 序列化是将对象编码为二进制格式,而 Chronicle Wire 也可以编码为许多不同的格式。编码会影响用于存储数据的字节数,格式越紧凑,使用的字节越少。Chronicle Wire 平衡了格式的紧凑性,而不会过度压缩数据,这将占用宝贵的 CPU 时间,Chronicle Wire 旨在灵活且向后兼容,但性能也非常高。在不牺牲性能的情况下以尽可能少的字节存储数据,例如,可以使用停止位编码存储整数。

一些编码的性能更高,也许通过不对字段名称进行编码以减小编码数据的大小,这可以通过使用 Chronicle Wire 的 Field Less Binary 来实现。然而,这是一个权衡,有时最好牺牲一点性能并添加字段名称,因为它将为我们提供向前和向后兼容性。

不同的格式
Chronicle Wire 有多种实现,每种实现在不同的场景中都很有用。例如,当我们想要提供应用程序配置文件或创建数据驱动的测试时,我们通常希望将对象序列化或反序列化为人类可读的格式,如 YAML、JSON。此外,能够将序列化为类型化 JSON 的 java 对象允许我们从应用程序的 JavaScript UI 层发送和接收消息。

有时,能够在编码格式之间进行互操作非常重要。一个例子是开源产品 Chronicle Queue。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 ··汽车·····,·Lewi
00000010 73 20 48 61 6d 69 6c 74 6f 6e s 哈米尔特 上

反序列化示例
到目前为止,所有示例都涵盖了序列化,因此在反序列化方面,我们可以从数据开始,例如:

{"@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 个),但是,如果我们选择使用基本编码器,并假设我们可以将字符串限制为以下字符“.ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+“[这是一些最常用的字符],那么我们可以使用这个基本编码器将上面的 10 个字符存储到 8 个字节中。

当然,您可以创建自己的基本编码,它不仅必须包含这么多字符。字符数更少,您可以从更紧凑的性能中受益。如前所述,我们可以使数据越紧凑,读取和写入的速度就越快。

下面是 Chronicle Wire 如何以 long 形式存储小字符串的示例,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 标准序列化更高的性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值