查看原文
其他

源码分析kryo对象序列化实现原理

微信公众号:[中间件兴趣圈]
作者简介:《RocketMQ技术内幕》作者

本文主要梳理Kryo序列化基本实现。重点剖析Kryo#writeClassAndObject、Kryo#readClassAndObject方法。

Kryo对象序列化原理

kryo对象序列化入口为Kryo的writeClassAndObject。

Kryo#writeClassAndObject

1public void writeClassAndObject (Output output, Object object) {
2    if (output == nullthrow new IllegalArgumentException("output cannot be null.");
3    beginObject();                                             // @1
4    try {
5        if (object == null) {
6            writeClass(output, null);              // @2
7            return;
8        }
9        Registration registration = writeClass(output, object.getClass());   // @3
10        if (references && writeReferenceOrNull(output, objectfalse)) {    // @4
11            registration.getSerializer().setGenerics(thisnull);
12            return;
13        }
14        if (TRACE || (DEBUG && depth == 1)) log("Write"object);
15        registration.getSerializer().write(this, output, object);  // @5
16    } finally {
17        if (--depth == 0 && autoReset) reset();       // @6
18    }
19}

代码@1:开始序列化, 将dept自增,表示当前深度,因为在序列化一个对象时,该方法有可能会被递归调用,每递归调用增加1,一次调用结束后在finally字句中自减。

代码@2:如果对象为空,则调用writeClass(DefaultSerializers ClassSerializer),序列化为空。

代码@3:如果对象不为空,首先序列化对象所属的Class实例,从这里可以看出,Kryo在序列化时,首先先序列化类型。

代码@4:如果references 为true(默认为true,可以序列化循环依赖),则调用writeReferenceOrNull序列化。

代码@5:如果references 为false,则调用write序列化,此时如果对象存在循环依赖,则会抛出 throw new KryoException("Max depth exceeded: " + depth)异常,如果object为基本类型,也将通过该方法完成值的序列化。

代码@6:完成序列化后,恢复相关数据。也就是说Kryo实例并不是线程安全的。
默认references 为true,表示支持循环嵌套,我们接下来重点跟踪一下writeReferenceOrNull方法。

writeReferenceOrNull方法

1/** @param object May be null if mayBeNull is true.
2     * @return true if no bytes need to be written for the object. */

3    boolean writeReferenceOrNull (Output output, Object object, boolean mayBeNull) {     // @1
4        if (object == null) {                                                                                                    // @2                    
5            if (TRACE || (DEBUG && depth == 1)) log("Write"null);
6            output.writeVarInt(Kryo.NULL, true);
7            return true;
8        }
9        if (!referenceResolver.useReferences(object.getClass())) {                                  // @3
10            if (mayBeNull) output.writeVarInt(Kryo.NOT_NULL, true);
11            return false;
12        }
13
14        // Determine if this object has already been seen in this object graph.
15        int id = referenceResolver.getWrittenId(object);                                                 // @4
16
17        // If not the first time encountered, only write reference ID.
18        if (id != -1) {                                                                                                        // @5
19            if (DEBUG) debug("kryo""Write object reference " + id + ": " + string(object));
20            output.writeVarInt(id + 2true); // + 2 because 0 and 1 are used for NULL and NOT_NULL.
21            return true;
22        }
23
24        // Otherwise write NOT_NULL and then the object bytes.
25        id = referenceResolver.addWrittenObject(object);     // @6
26        output.writeVarInt(NOT_NULL, true);
27        if (TRACE) trace("kryo""Write initial object reference " + id + ": " + string(object));
28        return false;                                                              // @7
29    }

代码@1:参数说明:Output output:输出流;Object object:待序列化的对象;

代码@2:如果对象为空,写入Kryo.NULL(0),然后返回true,表示需要设置generic,后续会讲解一下generic(泛型支持)。

代码@3:如果是基本类型,如果maybe(值可能为空),但该方法不为空,则设置为Kryo.NOT_NULL,然后返回false,表示非引用类型,需要持久化值。

代码@4:判断该对象是否在对象图中已被序列化一次。(其实现方式ListReferenceResolver、MapReferenceResolver)。

ListReferenceResolver#getWrittenId

1public int getWrittenId (Object object) {
2    for (int i = 0, n = seenObjects.size(); i < n; i++) {
3        if (seenObjects.get(i) == object) {
4            return i;
5        }
6    }
7    return -1;
8}

代码@5:如果writtenId不等于-1,表示该对象已被序列化,直接序列化ID,直接返回true,然后结束writeClassAndObject该方法,表示该对象实例完成。

代码@6:为object构建一个ID,这个ID数据是在一次嵌套调用writeClassAndObject内有效,然后writeClassAndObject结束后,会调用reset方法,将其清空,然后先写入为空标识,并返回false,也就是第一次序列化对象时,返回false,会进入到writeClassAndObject的代码@5中。
Kryo#writeClassAndObject 代码@5

1registration.getSerializer().write(this, output, object);  // @5

其实其重点关键,还是writeClassAndObject#writeClass也就是上文说的代码@3,在序列化对象之前,首先先序列化该对象的类型,然后需要返回对应的字段序列器。例如,如果类的类型为java.util.Map,则首先先要记录类型为Map,然后返回可以序列化Map的序列器,再例如类型如果是java.lang.String,则先序列化类型,然后序列化值,序列化值的序列器则为StringSerializer,那如果是一个对象类型,例如cn.uce.demo.Student,自然第一步是先序列化类型cn.uce.demo.Student,接下来就需要序列化Student的各个字段的信息,返回的序列化为FieldSerializer,然后可以通过FieldSeriaizer返回Student的属性列表,然后单独一个字段一个字段的序列化,其顺序也就是,先类型,再序列化值。这样就递归完成了一个对象的序列化操作。

Kryo序列化实现原理总结

1、先序列化类型(Class实例),然后根据类型返回相应的序列化器(上一篇详细介绍了各种类型的序列化器)。

2、再序列化该类型的值。

3、如果自定义类型,例如(cn.uce.demo.Student),则返回的值序列化器为DefaultSerializers$FieldSerializer,然后一个字段一个字段的序列化,当然其序列化类型也是,先类型再值的模式,递归进行,最终完成。

4、引入了对象图的概念来消除循环依懒的序列化,已序列化的对象,在循环引用时,只是用一个int类型来表示该对象值,类似一种缓存的概念。

Kryo与java 序列化的区别

kryo的设计目的是指对象值的序列化,关注的是有效数据的传输,减少需要序列化的元数据信息。

这一点通过Kryo对Class对象的序列化,也就是类型的序列化就能看出端倪。Kryo对Class的序列化只需要化Class的全路径名,在反序列化时根据Class通过类加载进行加载,大大减少了序列化后的文件大小,能极大提高性能。

Kryo的核心设计理念就是尽最大可能减少序列化后的文件大小,其举措1就是通过对long,int等数据类型,采用变长字节存储来代替java中使用固定字节(4,8)字节的模式,因为在软件开发中,对象的这些值基本上都是小值,能节省很多空间,第二个举措是使用了类似缓存的机制,在一次序列化对象中,在整个递归序列化期间,相同的对象,只会序列化一次,后续的用一个局部int值来代替。



广告:作者新书《RocketMQ技术内幕》已上市

《RocketMQ技术内幕》已出版上市,目前可在主流购物平台(京东、天猫等)购买,本书从源码角度深度分析了RocketMQ NameServer、消息发送、消息存储、消息消费、消息过滤、主从同步HA、事务消息;在实战篇重点介绍了RocketMQ运维管理界面与当前支持的39个运维命令;并在附录部分罗列了RocketMQ几乎所有的配置参数。本书得到了RocketMQ创始人、阿里巴巴Messaging开源技术负责人、Linux OpenMessaging 主席的高度认可并作序推荐。目前是国内第一本成体系剖析RocketMQ的书籍。
新书7折优惠!7折优惠!7折优惠!



更多文章请关注微信公众号:

推荐关注微信公众号:RocketMQ官方微信公众号

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存