概念
(1)基础概念和用途
序列化和反序列本质上就是对象和字节数组的转换:
序列化时,将Java对象编码为byte数组
反序列化,则是将byte数组转换为Java对象
序列化用途: 1、在网络上传送对象的字节序列
2、把对象的字节序列永久地保存到硬盘上,通常放在一个文件中
(2)java序列化的实现
1、ObjectOutputStream:通过writeObject方法将对象序列化,并将得到的字节序列写到目标输出流
try {
ObjectOutputStream output =new ObjectOutputStream(new FileOutputStream("oppo.text"));
output.writeObject(new Regulation<>());
}catch (Exception e){
}
2、ObjectInputStream:通过readObject方法从输入流中读取字节序列,并反序列化一个对象
try {
ObjectInputStream input=new ObjectInputStream(new FileInputStream("oppo.txt"));
Object o = input.readObject()
}catch (Exception e){
}
3、实现Sericalizable接口,实现Sericalizable接口的时候还要写一个SericalizableUID(火眼质量代码扫描可能会不通过),这个是版本号,JVM会把传进来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。如果实现接口的时候,没有给定UID,就会使用默认的UID,当使用默认的UID的时候,jvm每次编译的时候会生成一个UID,当后面程序改了一些代码,再次编译的时候会生成不同的UID,会导致反序列化失败!所以在实现Sericalizable接口的时候,我们自己给定一个固定的UID值,这样就能保证编译完再 反序列化的时候的版本一致性。所以能不能成功反序列化,就是看对象中的UID和实体中UID是否一致。
4、实现Externalnalizable接口,在类中实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法(序列化的细节需要由开发人员自己实现,与是否被transient修饰无关),在方法中定义类对象自定义的序列化和反序列化操作。这样通过对象输出流和对象输入流的输入输出方法序列化和反序列化对象时会自动调用类中定义的readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法。
注:
1)JDK中除了提供 Serializable 序列化接口外,还提供了另一个序列化接口Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。这是因为源码中Externalizable 接口继承了 Serializable 接口。并定义了两个方法 writeExternal 和 readExternal 方法,因此Externalizable 的序列化机制优先级要高于 Serializable。
2)使用 Externalizable 进行序列化时,必须要有默认的构造方法,而Serializable可以没有默认的构造方法。
一、PB序列化
PB序列化是指将Protocol Buffer(简称PB)数据结构转换为字节流的过程。PB是Google开发的一种高效、可扩展、平台无关的数据交换格式,常用于RPC协议、数据存储和数据交换等领域。
PB官方git:https://github.com/protocolbuffers/protobuf
(1)PB序列化的优点
- 体积小,序列化后的数据结构一般比XML和JSON更小。
- 速度快,PB序列化和反序列化的速度通常比XML和JSON更快。
- 语言支持广泛,PB提供了多种语言的API和代码生成工具,可以方便地在不同的平台上使用。
- 可扩展性强,PB提供了扩展机制,即使原来的数据结构发生变化,也可以通过向PB消息中添加新的字段来实现升级。 PB序列化的具体实现方式包括:
- 定义PB消息的.proto文件。
- 使用编译器将.proto文件生成对应的代码。
- 在代码中使用PB提供的API将对象序列化为字节流,或者将字节流反序列化为对象。 例如,下面是一个简单的.proto文件定义了一个用户信息的PB消息:
syntax = "proto3";
message UserInfo {
string name = 1;
int32 age = 2;
repeated string interests = 3;
}
通过编译器生成的代码可以使用下面的方式创建和序列化一个UserInfo对象:
UserInfo user = UserInfo.newBuilder().setName("Alice").setAge(30)
.addInterests("Programming").addInterests("Reading").build();
byte[] bytes = user.toByteArray();
反序列化的方式类似:
byte[] bytes = ... // 从文件或网络读取字节流
UserInfo user = UserInfo.parseFrom(bytes);
总的来说,PB序列化是一种高效而强大的数据交换方式,适用于需要高性能、高可扩展性,并且对数据体积有要求的场景。
(2)为什么需要@Tag注解
@Tag 是 Protocol Buffers 中的一个注解,用于指定一个字段的标签号(tag),该标签号必须是一个正整数。在序列化和反序列化中,每个字段都需要标签号来标记该字段的类型和位置,只有使用 @Tag 才能保证序列化和反序列化时字段的匹配性和正确性。如果没有使用 @Tag,则默认使用该字段在代码中的位置作为 tag,在代码修改后可能会导致 tag 发生改变,从而导致序列化和反序列化出错。因此,使用 @Tag 是为了保证数据的稳定性和一致性。
遗留:
如何实现节省空间?
各类json的选型?
使用场景?dubbo默认hessian,被替换的原因?
JDK11版本无法反序列化问题?调活动RPC问题?
二、avro序列化
Apache Avro是一个数据序列化系统
(1)Avro提供
- 丰富的数据结构
- 一个紧凑的,快速的,二进制的数据格式
- 一个容器文件,来存储持久化数据
- 远程过程调用(RPC)
- 简单的动态语言集成
- 代码生成不需要读写数据文件,也不要使用或实现RPC协议。代码生成是作为一个可选的优化,只对静态类型的语言值得实现。
- .avro本质只是一种数据格式
(2)scheme
Avro依赖于模式(Schema)。通过模式定义各种数据结构,只有确定了模式才能对数据进行解释,所以在数据的序列化和反序列化之前,必须先确定模式的结构。
Avro作为RPC框架来使用。客户端希望同服务器端交互时,就需要交换双方通信的协议,它类似于模式,需要双方来定义,在Avro中被称为消息(Message)。通信双方都必须保持这种协议,以便于解析从对方发送过来的数据,这也就是握手阶段。
(3)特点
- 跨语言
- 可压缩可切割
- 数据结构丰富(null、boolean、int、long、float、double、bytes、string、Records、Enums、Arrays、Maps、Unions、Fixed)
- json定义scheme
- 自描述语言(scheme是附加在data中同时传输的)
三、Json序列化
(1)JSON的身份证
全名:JavaScript Object Notation
户籍:Json是一种轻量级数据交换格式,它采用的是完全独立于编程语言之外的文本格式,同时它也使用了类似c家族的习惯,这些特性使得json成为最理想的数据交换语言。
特点:
1)JSON文本格式的本质就是具有特定格式的字符串
2)JSON比XML有效性更高
(2)JSON的数据格式
JSON对象和JSON数组
示例:
String json1 = "{"id":1,"name":"Tom"}";
String json2 = "[12,"abc",{"id":1,"name":"Tom"}]";
Json对象:
结构:{key1:value1,key2:value2}
格式描述:在键值对与键值对之间用","隔开,在键与值之间用:隔开
key的数据类型:字符串
value的数据类型:字符串,数值,JSON对象,JSON数组
Json数组:
结构:[value1,value2,value3]
value的数据类型:字符串,数值,JSON对象,JSON数组
注:JSON的对象和数组可以作为Json对象数组的value数据类型
(3)JSON的解析方向
Json对象对应的是Java中的对象
Json数组对应的是Java中的List集合
(4)各种json工具包的比较
JSON不管是在Web开发还是服务器开发中是相当常见的数据传输格式,一般情况我们对于JSON解析构造的性能并不需要过于关心,除非是在性能要求比较高的系统。
四个JSON类库分别为:Gson,FastJson,Jackson,Json-lib。
简单介绍下四个类库的身份背景。
1)Gson(项目地址:https://github.com/google/gson)。
Gson是目前功能最全的Json解析神器,Gson当初是为因应Google公司内部需求而由Google自行研发而来,但自从在2008年五月公开发布第一版后已被许多公司或用户应用。Gson的应用主要为toJson与fromJson两个转换函数,无依赖,不需要例外额外的jar,能够直接跑在JDK上。而在使用这种对象转换之前需先创建好对象的类型以及其成员才能成功的将JSON字符串成功转换成相对应的对象。类里面只要有get和set方法,Gson完全可以将复杂类型的json到bean或bean到json的转换,是JSON解析的神器。
Gson gson = new Gson();
Object object = gson.fromJson(json, Object.class);//json对象->java对象
List<Object> list = gson.fromJson(json, new TypeToken<List<Object>>() {
}.getType());//json对象->java对象list
private Type type = new TypeToken<List<Object>>(){}.getType();//泛型只在编译时有效,运行时泛型擦除
String json = gson.toJson(object);//java对象->json对象
注:gson可能会导致cpu飙高的情况
2)FastJson(项目地址:https://github.com/alibaba/fastjson)。Fastjson是一个Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。无依赖,不需要例外额外的jar,能够直接跑在JDK上。FastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,导致Json转换出错,需要制定引用。FastJson采用独创的算法,将解析的速度提升到极致,超过所有json库。
源码阅读博客:Fastjson源码阅读(一):前言_fastjson源码解析-CSDN博客
使用:利用@JSONField注解
String json = JSON.toJSONString(Object object);//java对象->json对象
Object object = JSON.parseObject(json);//json对象->java对象
注意事项:当对象的某个成员变量为null时,序列化时不会创建相关字段;要用相关的封装类来修改基础数据类型变量
3)Jackson(项目地址:https://github.com/FasterXML/jackson)。相比json-lib框架,Jackson所依赖的jar包较少,简单易用并且性能也要相对高些。而且Jackson社区相对比较活跃,更新速度也比较快。Jackson对于复杂类型的json转换bean会出现问题,一些集合Map,List的转换出现问题。Jackson对于复杂类型的bean转换Json,转换的json格式不是标准的Json格式。
4)Json-lib(项目地址:Maven - Json-lib::Welcome)。json-lib最开始的也是应用最广泛的json解析工具,json-lib 不好的地方确实是依赖于很多第三方包,包括commons-beanutils.jar,commons-collections-3.2.jar,commons-lang-2.6.jar,commons-logging-1.1.1.jar,ezmorph-1.0.6.jar,对于复杂类型的转换,json-lib对于json转换成bean还有缺陷,比如一个类里面会出现另一个类的list或者map集合,json-lib从json到bean的转换就会出现问题。json-lib在功能和性能上面都不能满足现在互联网化的需求。
5)选择一个合适的JSON库要从多个方面进行考虑:字符串解析成JSON性能、字符串解析成JavaBean性能、JavaBean构造JSON性能、集合构造JSON性能、易用性
使用选择原则:
- 字符串解析成JavaBean:当数据量较少时首选FastJson,数据量较大使用Jackson。但是Jackson无法堆一个对象集合进行解析,只能转成一个Map集合,这点Gson和FastJson处理的比较好。
- 字符串解析成JSON:当数据量较少时首选FastJson,数据量较大使用Jackson。
- JavaBean构造JSON:当数据量较少时选择Gson,数据量较大可使用Jackson。
- 集合构造JSON:首先Jackson,其次Fastjson。
- 从易用性角度分析,FastJson的API设计的最简单,最方便使用,直接使用JSON的两个静态方法即可完成四种操作;而Gson和Jackson都需要new一个对象,虽然这个对象可以复用,但是在实际使用过程中还需要用一个全局变量来保存改变量,同时API设计的也不是很好理解,对于FastJson来说复杂的API是因为他支持流式解析,适合对JSON进行大量且复杂的操作,但是实际应用中对于JSON的操作都是简单的解析成JavaBean,然后JavaBean序列化成JSON字符串即可,复杂的操作很少。
(5)总结
四、hessian序列化
官方地址:Hessian 2.0 Serialization Protocol
(1)特点
参考官方文档的描述:
- 它必须自我描述序列化类型,即不需要外部模式或接口定义
- 它必须是独立于语言的,包括支持脚本语言
- 它必须是可以通过单一方式进行读写
- 它必须尽可能紧凑
- 它必须简单,这样才能有效地测试和实现
- 必须尽可能地快、高效
- 它必须支持Unicode字符串
- 它必须支持8位二进制数据,而不需要转义或使用附件
- 它必须支持加密、压缩、签名和事务上下文信封
(2)hessian协议与jdk区别
- 区别一:java序列化无法跨语言
- 区别二:新旧对象的版本Java通过一个serialVersionUID来关联,需要开发者关注序列化的语义
- 区别三:java序列化不支持加密
- 区别四:Java序列化的内容比hessian大
(3)hessian总结
- 序列化对象要实现 Serializable 接口,否则序列化时会报"must implement java.io.Serializable"异常
- 若序列化对象经hessian序列化后,序列化对象中不加serialVersionUID时,再改变(增加对象属性、删除对象属性)都不会产生反序列化异常,即hessian序列化对象不再需要serialVersionUID。===
- hessian会把复杂对象所有属性存储在一个 Map 中进行序列化。所以在父类、子类存在同名成员变量的情况下, Hessian 序列化时,先序列化子类,然后序列化父类,因此反序列化结果会导致子类同名成员变量被父类的值覆盖。
- hessian中的writeReplace方法与readResolve方法的作用一样,如果序列化的对象具有此方法,会利用此方法返回的实例来代替序列化后实例,用以保证对象的单例性。