这周跟字节的大佬聊了聊,问了问职业规划与发展,总结了一下,就是要持续学习,同时跟我讲要多些博客,将别人的东西通过自己说出来。
最近在做服务间Feign远程调用的时候,对生产者和消费者之间的实体类返回值映射比较好奇,于是学习一番。
1. 序列化概述
1.1 定义
序列化是指把对象转成字节流的过程 反序列化是指把字节流转换成对象的过程
1.2 为什么要序列化
- 对象持久化:java对象是在内存里,当系统故障重启java对象相关信息随之消失,因此一些场景要求持久化java对象以保证重启jvm之后也能够恢复之前的状态。如游戏的存档,配置管理等。
- 跨平台兼容性:不同的操作系统、硬件平台、编程语言所能处理的对象格式肯定各不相同,将对象序列化成标准的、大家都能处理的字节流格式,确保了跨平台、跨系统的兼容性。
- 方便网络传输:序列化之后的对象体积被压缩,减少了网络传输所需的带宽,同时需要传输对象的场景很多,如分布式系统的状态保持,RMI(远程方法调用),消息队列,Redis缓存,分布式系统的数据共享(Hadoop等)。
1.3 怎么实现序列化
目前有各种各样的序列化开源框架,包括java原生的序列化技术、Hessian、protobuf、JSON、XML、MessagePack、Kyro。各个虚拟化技术都有其应用场景和缺点。
2. Java标准序列化
2.1 Demo
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L; // 建议显式指定版本号
private String name;
private transient int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
import java.io.*;
public class JavaSerializationDemo {
public static void main(String[] args) {
User user = new User("John Doe", 30);
// 序列化对象
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
oos.writeObject(user);
System.out.println("对象已序列化到文件 user.ser");
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
User deserializedUser = (User) ois.readObject();
System.out.println("从文件 user.ser 反序列化对象: " + deserializedUser);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
2.2 要点
- 实现
Serializable
接口:Serializable
接口起着标记作用,告诉JVM实现了该接口的对象是可以被序列化的,真正的序列化操作并不由其来完成。 serialVersionUID
:序列化ID,在反序列化时,会将字节流中的serialVersionUID
与被序列化类中的serialVersionUID
进行比较,一致则可以进行反序列化,不一致则抛出序列化版本不一致的异常。transient
关键字:被该关键字修饰的字段,不会被序列化,比如一些密码,或者不需要传输等信息。可以减少传输的数据量。
3. Hessian
Hessian在RPC场景使用的比较多,如Dubbo、XXL-JOB 其序列化速度较快,同时生成的二进制流较小
public static void main(String[] args) throws Exception {
Score object = Score.builder()
.className("一班")
.stuName("张三").course("生物").score(90).build();
long startTime = System.currentTimeMillis();
//序列化
ByteArrayOutputStream os = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(os);
output.writeObject(object);
output.close();
byte[] bytes = os.toByteArray();
//反序列化
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
Hessian2Input input = new Hessian2Input(in);
System.out.println(input.readObject());
input.close();
System.out.println("耗时:"+(System.currentTimeMillis()-startTime)/1000.00);
}
3.1 要点
- 序列化的对象要实现Serializable接口,否则会报异常
- Hessian会把所有复杂对象的属性存储在一个Map中,在父类、子类有相同属性的时候,先序列化子类对象,再父类对象,会导致子类对象的属性被父类覆盖