什么是序列化和反序列化?
如果需要持久化Java对象比如将Java对象保存在文件中, 或在网络传输Java对象, 这些场景需要使用到序列化.
序列化
将数据结构或对象转化成二进制字节流的过程
反序列化
将在序列化过程中所产生的二进制字节流转换成数据结构或者对象的过程
目的
序列化的主要目的是 通过网络传输对象 或者是将 对象存储到文件系统,数据库或内存 中.
常见序列化协议
JDK自带的序列化方式,因为序列化效率低而且存在安全问题, 一般不会使用.
像JSON和XML这种属于文本类序列化方式, 虽然可读性好, 但是性能较差, 一般不会选择
常用的基于二进制序列化协议有: Hessian, Kryo, Protobuf, ProtoStuff
JDK 自带的序列化方式
JDK自带的序列化, 只需要实现java.io.Serializable
接口即可, 如下所示:
/**
* @author 花木凋零成兰
* @title SerializableTest
* @date 2024/4/26 16:41
* @package com.example.demo.demo1
* @description 测试JDK的序列化方式
*/
public class SerializableTest implements Serializable {
private static final long serialVersionUID = 1L;
private String filed1;
private String[] filed2;
private Object parameters;
}
serialVersionUID有什么作用呢?
主要用于版本控制, 反序列化时, 会检查serialVersionUID
是否符合与当前类的serialVersionUID
一致, 不一致则会抛出InvalidClassException
异常
推荐每个序列化类都手动指定其serialVersionUID
, 如果不手动指定, 则编译器会动态生成默认的serialVersionUID
serialVersionUID不是被static修饰了吗? 为什么还会序列化?
static
修饰的变量是静态变量, 位于方法区, 本身不会被序列化, 但是serialVersionUID
的序列化做了特殊处理, 序列化时, 会将其序列化到二进制字节流中, 在反序列化时也会解析并做一致性判断
即, serialVersionUID
只是用来被JVM识别, 实际并没有被序列化
有些字段并不想序列化怎么办?
对于不想序列化的字段, 可以使用transient
关键字修饰; 其作为为: 阻止实例中用此关键字修饰的变量序列化, 对象被反序列化时, 被该关键字修饰的变量值不会被持久化和恢复
关于transient
注意:
- 只能修饰变量, 不能修饰类和方法
- 修饰的变量, 在被反序列化后变量值会被置成变量类型的默认值, 例如,修饰
int
类型, 那么反序列化后结果就是0 static
变量,因为不属于任何对象, 所以无论有无transient
关键字修饰, 都不会被序列化
为什么不推荐使用JDK序列化
- 不支持跨语言调用
- 性能差; 序列化后的字节数组体积较大, 传输成本加大
- 存在安全问题
Kryo
是一个高性能的序列化/反序列化工具, 由于其变长存储特性并使用了字节码生成机制, 拥有较高的运行速度和较小的字节码体积
另外, Kryo序列化实现趋于成熟, 且在多个著名开源项目中广泛使用
github地址
使用示例如下:
/**
* @author 花木凋零成兰
* @title HelloKryo
* @date 2024/4/26 17:14
* @package com.example.demo.demo1
* @description 尝试Kryo序列化
*/
public class HelloKryo {
static public void main (String[] args) throws Exception {
// 创建Kryo实例
Kryo kryo = new Kryo();
// 使用register方法注册一个类SomeClass 让Kryo知道应该序列化的类
kryo.register(SomeClass.class);
// 创建SomeClass实例
SomeClass object = new SomeClass();
object.value = "Hello Kryo!";
// 创建文件路径对象 且文件名为file.bin
Path path = Paths.get("file.bin");
// 创建输出对象 用于写入序列化数据
// Files.newOutputStream(path) 打开输出流 用于写入指定路径的文件
Output output = new Output(Files.newOutputStream(path));
// 将object序列化后的二进制数据 写入到output流中
kryo.writeObject(output, object);
// 关闭output流
output.close();
// 创建输入对象 读取序列化数据
Input input = new Input(Files.newInputStream(path));
// 将读取的二进制数据 反序列化为SomeClass对象实例
SomeClass object2 = kryo.readObject(input, SomeClass.class);
System.out.println(object2.value);
// 关闭输入读取流
input.close();
}
static public class SomeClass {
String value;
}
}
Protobuf
出自于Google, 性能比较优秀, 支持多种语言和跨平台, 但是使用过于繁琐, 需要自定义 LDL 文件和生成对应的序列化代码, 不够灵活, 但是也保证了没有序列化漏洞的风险
github地址
ProtoStuff
ProtoStuff 基于 Google Protobuf, 提供了更多的功能和更简易的用法, 同时性能也很优秀
github地址
Hessian
是轻量级,自定义描述的二进制RPC协议, 支持跨语言, 但是是比较老的序列化实现
总结
Kryo是专门针对Java语言序列化方式其性能非常好,所以推荐使用Kryo来作为序列化方式,
参考
https://github.com/Snailclimb/guide-rpc-framework