关于java:一个高性能小而美的序列化工具

5次阅读

共计 3701 个字符,预计需要花费 10 分钟才能阅读完成。

作者:fredalxin\
地址:https://fredal.xin/kryo-quickstart

Kryo 是一个高性能的序列化 / 反序列化工具,因为其变长存储个性并应用了字节码生成机制,领有较高的运行速度和较小的体积,在某些场景中成为了除 Json、Protobuf 之外的抉择。

依赖

首先咱们引入 maven 的相干依赖:

<dependency>
    <groupId>com.esotericsoftware</groupId>
    <artifactId>kryo</artifactId>
    <version>4.0.2</version>
</dependency>

须要留神的是,因为 kryo 应用了较高版本的 asm,可能会与业务现有依赖的 asm 产生抵触,这是一个比拟常见的问题。只需将依赖改成:

<dependency>
    <groupId>com.esotericsoftware</groupId>
    <artifactId>kryo-shaded</artifactId>
    <version>4.0.2</version>
</dependency>

记录类型信息

这算是 kryo 的一个特点,能够把对象信息间接写到序列化数据里,反序列化的时候能够准确地找到原始类信息,不会出错,这意味着在写 readxxx 办法时,无需传入 Class 或 Type 类信息。

相应的,kryo 提供两种读写形式。记录类型信息的 writeClassAndObject/readClassAndObject 办法,以及传统的 writeObject/readObject 办法。

线程平安

kryo 的对象自身不是线程平安的,所以咱们有两种抉择来保障线程平安。

应用 Threadlocal 来保障线程平安:

private static final ThreadLocal<Kryo> kryoLocal = new ThreadLocal<Kryo>() {protected Kryo initialValue() {Kryo kryo = new Kryo();
        kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
        return kryo;
    };
};

或者应用 kryo 提供的 pool:

public KryoPool newKryoPool() {return new KryoPool.Builder(() -> {final Kryo kryo = new Kryo();
        kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
        return kryo;
    }).softReferences().build();
}

实例化器

在下面留神到kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy())); 这句话显示指定了实例化器。

在一些依赖了 kryo 的开源软件中,可能因为实例化器指定的问题而抛出空指针异样。例如 hive 的某些版本中,默认指定了 StdInstantiatorStrategy。

public static ThreadLocal<Kryo> runtimeSerializationKryo = new ThreadLocal<Kryo>() {
    @Override
    protected synchronized Kryo initialValue() {Kryo kryo = new Kryo();
        kryo.setClassLoader(Thread.currentThread().getContextClassLoader());
        kryo.register(java.sql.Date.class, new SqlDateSerializer());
        kryo.register(java.sql.Timestamp.class, new TimestampSerializer());
        kryo.register(Path.class, new PathSerializer());
        kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
        ......
            return kryo;
    };
};

而 StdInstantiatorStrategy 在是根据 JVM version 信息及 JVM vendor 信息创建对象的,能够不调用对象的任何构造方法创建对象。

那么例如碰到 ArrayList 这样的对象时候,就会出问题。察看一下 ArrayList 的源码:

public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

既然没有调用结构器,那么这里 elementData 会是 NULL,那么在调用相似 ensureCapacity 办法时,就会抛出一个异样。

 public void ensureCapacity(int minCapacity) {
     if (minCapacity > elementData.length
         && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
              && minCapacity <= DEFAULT_CAPACITY)) {
         modCount++;
         grow(minCapacity);
     }
 }

解决方案很简略,就如框架中代码写的一样,显示指定实例化器,首先应用默认无参结构策略 DefaultInstantiatorStrategy,若创建对象失败再采纳 StdInstantiatorStrategy。

类注册

当 kryo 写一个对象的实例的时候,默认须要将类的齐全限定名称写入。将类名一起写入序列化数据中是比拟低效的,所以 kryo 反对通过类注册进行优化。

kryo.register(SomeClassA.class);
kryo.register(SomeClassB.class);
kryo.register(SomeClassC.class);

注册会给每一个 class 一个 int 类型的 Id 相关联,这显然比类名称高效,但同时要求反序列化的时候的 Id 必须与序列化过程中统一。这意味着注册的程序十分重要。

然而因为事实起因,同样的代码,同样的 Class 在不同的机器上注册编号任然不能保障统一,所以多机器部署时候反序列化可能会呈现问题。

所以 kryo 默认会禁止类注册,当然如果想要关上这个属性,能够通过 kryo.setRegistrationRequired(true); 关上。

循环援用

这是对循环援用的反对,能够无效避免栈内存溢出,kryo 默认会关上这个属性。当你确定不会有循环援用产生的时候,能够通过 kryo.setReferences(false); 敞开循环援用检测,从而进步一些性能。

可变长存储

kryo 对 int 和 long 类型都采纳了可变长存储的机制,以 int 为例,个别须要 4 个字节去存储,而对 kryo 来说,能够通过 1 - 5 个变长字节去存储,从而防止高位都是 0 的节约。

最多须要 5 个字节存储是因为,在变长存储 int 过程中,一个字节的 8 位用来存储有效数字的只有 7 位,最高位用于标记是否还需读取下一个字节,1 示意须要,0 示意不须要。

在对 string 的存储中也有变长存储的利用,string 序列化的整体构造为 length+ 内容,那么 length 也会应用变长 int 写入字符的长度。

配合缓存应用的场景

在理论开发中,class 增删字段是很常见的事件,但对于 kryo 来说,确是不反对的,而如果恰好须要应用缓存,那么这个问题会被放得更大。

例如一个对象应用 kryo 序列化后,数据放入了缓存中,而这时候如果这个对象增删了一个属性,那么缓存中反序列化的时候就会报错。所以频繁应用缓存的场景,能够尽量避免 kryo。

不过当初的 Kryo 提供了兼容性的反对,应用 CompatibleFieldSerializer.class,在 kryo.writeClassAndObject 时候写入的信息如下:

class name|field length|field1 name|field2 name|field1 value| filed2 value

而在读入 kryo.readClassAndObject 时,会先读入 field names,而后匹配以后反序列化类的 field 和程序再结构后果。

当然如果在做好缓存隔离的状况下,这所有都不必在意。

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿(2021 最新版)

2. 终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!

3. 阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!

4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0