评价一个序列化算法优劣的两个重要指标是:
Java 语言本身提供了对象序列化机制,也是 Java 语言本身最重要的底层机制之一,Java 本身提供的序列化机制存在两个问题:
基于 JDK 序列化方式实现,JDK 提 供 了 Java 对 象 的 序 列 化 方 式 , 主 要 通 过 输 出 流java.io.ObjectOutputStream 和对象输入流 java.io.ObjectInputStream来实现。其中,被序列化的对象需要实现 java.io.Serializable 接口。
Java的序列化机制是通过判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是 InvalidCastException。
如果没有为指定的 class 配置 serialVersionUID,那么 java 编译器会自动给这个 class 进行一个摘要算法,类似于指纹算法,只要这个文件有任何改动,得到的 UID 就会截然不同的,可以保证在这么多类中,这个编号是唯一的。
serialVersionUID 显示的生成方式:
serialVersionUID 自动的生成方式:
序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。
Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
绕开 transient 机制的办法
在使用了transient关键字修饰某个属性,可以新增加两个私有方法,这样也能让该属性达到序列号的功能。
如下:
public class User implements Serializable {
private String name;
private int age;
private transient String hobby;
private void writeObject(ObjectOutputStream objectOutputStream)throws IOException {
objectOutputStream.defaultWriteObject();
objectOutputStream.writeObject(hobby);
}
private void readObject(ObjectInputStream objectInputStream)throws IOException , ClassNotFoundException{
objectInputStream.defaultReadObject();
hobby = (String) objectInputStream.readObject();
}
}
...
原理:writeObject和readObject这两个私有的方法,既不属于Object、也不是Serializable,为什么能够在序列化的时候被调用呢?
原因是:ObjectOutputStream使用了反射来寻找是否声明了这两个方法。因为 ObjectOutputStream
使用 getPrivateMethod,所以这些方法必须声明为 priate 以至于供ObjectOutputStream 来使用
同一对象两次(开始写入文件到最终关闭流这个过程算一次,在不关闭流的情况才能演示出效果)写入文件,打印出写入一次对象后的存储大小和写入两次后的存储大小,第二次写入对象时文件只增加了 少量的 字节(不是两倍关系)
Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,所以增加的 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系。该存储规则极大的节省了存储空间。
使用 JAVA 进行序列化有它的优点,也有它的缺点:
- 优点:JAVA 语言本身提供,使用比较方便和简单
- 缺点:不支持跨语言处理、 性能相对不是很好,序列化以后产生的数据相对较大
XML序列化的好处在于可读性好,方便阅读和调试。但是序列化以后的字节码文件比较大,而且效率不高,适用于对性能不高,而且 QPS 较低的企业级内部系统之间的数据交换的场景,同时 XML 又具有语言无关性,所以还可以用于异构系统之间的数据交换和协议。比如我们熟知的 WebService,就是采用 XML 格式对数据进行序列化的。
我们可以使用开源框架XStream
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
public class XmlSerialiable implements ISerialiable {
XStream xStream = new XStream(new DomDriver());
public <T> byte[] seria(T object) {
return xStream.toXML(object).getBytes();
}
public <T> T deSeria(byte[] data, Class<T> clazz) {
return (T)xStream.fromXML(new String(data));
}
}
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,相对于 XML 来说,JSON 的字节流更小,而且可读性也非常好。现在 JSON数据格式在企业运用是最普遍的
JSON 序列化常用的开源工具有很多:
这几种 json 序列化工具中,Jackson 与 fastjson 要比 GSON 的性能要好,但是 Jackson、GSON 的稳定性要比 Fastjson 好。而 fastjson 的优势在于提供的 api 非常容易使用。
Hessian 是一个支持跨语言传输的二进制序列化协议,相对于 Java 默认的序列化机制来说,Hessian 具有更好的性能和易用性,而且支持多种不同的语言实际上 Dubbo 采用的就是 Hessian 序列化来实现,只不过 Dubbo 对Hessian 进行了重构,性能更高。
Protobuf是Google的一种数据交换格式,它独立于语言、独立于平台。
Google 提供了多种语言来实现,比如 Java、C、Go、Python,每一种实现都包含了相应语言的编译器和库文件;Protobuf 使用比较广泛,主要是空间开销小和性能比较好,非常适合用于公司内部对性能要求高的 RPC 调用。 另外由于解析性能比较高,序列化以后数据量相对较少,所以也可以应用在对象的持久化场景中。但是要使用 Protobuf 会相对来说麻烦些,因为他有自己的语法,有自己的编译器。
使用步骤:
一、下载 protobuf 工具:https://github.com/google/protobuf/releases 找到
二、编写proto 文件user.proto
三、在 protoc.exe 安装目录下执行如下命令
.\protoc.exe --java_out=./ ./user.proto
四、运行并将生成的Java文件拷贝到项目中
核心原理: protobuf 使用 varint(zigzag)作为编码方式, 使用 T-L-V 作为存储方式。
varint 编码方式
varint 是一种数据压缩算法,其核心思想是利用 bit位来实现数据压缩。
zigzag 编码方式
对于负数的处理,protobuf 使用 zigzag 的形式来编码存储
存储方式
经过编码以后的数据,大大减少了字段值的占用字节数,然后基于 T-L-V 的方式进行存储。
主要体现在序列化后的数据体积小 & 序列化速度快,最终使得传输效率高;
序列化速度快的原因:
序列化后的数据量体积小(即数据压缩效果好)的原因:
这个地址有针对不同序列化技术进行性能比较 :https://github.com/eishay/jvm-serializers/wiki