源码分析Dubbo序列化-源码分析kryo序列化实现原理
云栖号资讯:【点击查看更多行业资讯】
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!
本文主要梳理 Kryo 序列化基本实现。重点剖析 Kryo # writeClassAndObject、Kryo # readClassAndObject 方法。
1、源码分析Kryo#writeClassAndObject
public void writeClassAndObject (Output output, Object object) { if (output == null) throw new IllegalArgumentException("output cannot be null."); beginObject(); // @1 try { if (object == null) { writeClass(output, null); // @2 return; } Registration registration = writeClass(output, object.getClass()); // @3 if (references && writeReferenceOrNull(output, object, false)) { // @4 registration.getSerializer().setGenerics(this, null); return; } if (TRACE || (DEBUG && depth == 1)) log("Write", object); registration.getSerializer().write(this, output, object); // @5 } finally { if (--depth == 0 && autoReset) reset(); // @6 } }
代码@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 方法。
1.1 源码分析writeReferenceOrNull方法
/** @param object May be null if mayBeNull is true. * @return true if no bytes need to be written for the object. */ boolean writeReferenceOrNull (Output output, Object object, boolean mayBeNull) { // @1 if (object == null) { // @2 if (TRACE || (DEBUG && depth == 1)) log("Write", null); output.writeVarInt(Kryo.NULL, true); return true; } if (!referenceResolver.useReferences(object.getClass())) { // @3 if (mayBeNull) output.writeVarInt(Kryo.NOT_NULL, true); return false; } // Determine if this object has already been seen in this object graph. int id = referenceResolver.getWrittenId(object); // @4 // If not the first time encountered, only write reference ID. if (id != -1) { // @5 if (DEBUG) debug("kryo", "Write object reference " + id + ": " + string(object)); output.writeVarInt(id + 2, true); // + 2 because 0 and 1 are used for NULL and NOT_NULL. return true; } // Otherwise write NOT_NULL and then the object bytes. id = referenceResolver.addWrittenObject(object); // @6 output.writeVarInt(NOT_NULL, true); if (TRACE) trace("kryo", "Write initial object reference " + id + ": " + string(object)); return false; // @7 }
代码@1:参数说明:Output output:输出流;Object object:待序列化的对象;
代码@2:如果对象为空,写入 Kryo.NULL(0),然后返回 true,表示需要设置 generic,后续会讲解一下 generic(泛型支持)。
代码@3:如果是基本类型,如果 maybe (值可能为空),但该方法不为空,则设置为 Kryo.NOT_NULL ,然后返回 false,表示非引用类型,需要持久化值。
代码@4:判断该对象是否在对象图中已被序列化一次。(其实现方式 ListReferenceResolver、MapReferenceResolver)。
代码@5:如果 writtenId 不等于 -1,表示该对象已被序列化,直接序列化 ID,直接返回 true,然后结束 writeClassAndObject该方法,表示该对象实例完成。
代码@6:为 object 构建一 个 ID,这个 ID 数据是在一次嵌套调用 writeClassAndObject 内有效,然后 writeClassAndObject 结束后,会调用 reset 方法,将其清空,然后先写入为空标识,并返回 false,也就是第一次序列化对象时,返回 false,会进入到 writeClassAndObject 的代码@5中。
Kryo#writeClassAndObject
代码@5
registration.getSerializer().write(this, output, object); // @5
复制代码
其
实其重点关键,还是 writeClassAndObject # writeClass 也就是上文说的代码 @3,在序列化对象之前,首先先序列化该对象的类型,然后需要返回对应的字段序列器。例如,如果类的类型为 java.util.Map,则首先先要记录类型为 Map,然后返回可以序列化 Map 的序列器,再例如类型如果是 java.lang.String,则先序列化类型,然后序列化值,序列化值的序列器则为 DefaultSerializers$StringSerializer,那如果是一个对象类型,例如 cn.uce.demo.Student,自然,第一步是先序列化类型 cn.uce.demo.Student,接下来就需要序列化 Student 的各个字段的信息,返回的序列化为 DefaultSerializers$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值来代替。
【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/live立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK
原文发布时间:2020-04-24
本文作者:中间件兴趣圈
本文来自:“掘金”,了解相关信息可以关注“掘金”
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
相见恨晚!Java 线上问题定位,从未如此简单
有没有这样一种感受,自己写的代码在开发、测试环境跑的稳得一笔,可一到线上就抽风,不是缺这个就是少那个反正就是一顿报错,线上调试代码又很麻烦,让人头疼得很。阿里巴巴出了一款名叫Arthas的工具,可以在线分析诊断Java代码,着实让人眼前一亮。 一、Arthas 是什么? Arthas(阿尔萨斯) 是阿里开源的一个Java在线分析诊断工具 二、Arthas 能解决啥问题? 在日常开发上线过程中,我们多多少少都会遇到下边这些问题,苦于无法在线调试,只能通过老鸟的经验来硬分析bug,效率上不去还总开口问别人答疑解惑,多少有些不好意思。 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗? 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现! 是否有一个全局视角来查看系统的运行状况? 有什么办法可以监控到JVM的实时运行状态? 线上代码有错误,不想重新发布?那能不能改class文件替换一下? 三、Arthas两种安...
- 下一篇
刚哥谈架构(六)-大数据的文件存储
上一次我们谈到了各种类型的数据库,今天我们来谈谈在大数据,尤其是Hadoop栈下的数据和文件的存储。 我们知道为了解决大数据的存储和处理问题,google最先设计了推出了Map/Reduce的算法,而hadoop就是Google的map/reduce的开源实现。Hadoop主要由分布式的文件系统HDFS(参考Google的GFS)和Map/Reduce计算这两块。但随着Spark等更强大的计算引擎的出现,很少再有人使用Hadoop的Map/Reduce来做计算了,但是对于海量数据/文件的存储,除了HDFS,还真没有更多更好的选择。所以我们就来看看在Hadoop下,文件存储的各种选项。 原始文本文件 首先,我们什么都不需要做,HDFS提供分布式的文件存储,那么我们就直接把原始的文本文件存储在HDFS上就好了。通常我们会使用诸如txt,csv,json,xml等文本格式的文件存储在HDFS上,然后由各种计算引擎加载,计算。 HDFS是按照块来存储文件的,缺省的设置一个块的大小是64M,那么假定我的文本文件是1G,它会被分成16个分区,由计算引擎(Spark,Map/Reduce)来并行的处...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果