序列化与反序列化

101 阅读3分钟

这周跟字节的大佬聊了聊,问了问职业规划与发展,总结了一下,就是要持续学习,同时跟我讲要多些博客,将别人的东西通过自己说出来。

最近在做服务间Feign远程调用的时候,对生产者和消费者之间的实体类返回值映射比较好奇,于是学习一番。

1. 序列化概述

1.1 定义

序列化是指把对象转成字节流的过程 反序列化是指把字节流转换成对象的过程

1.2 为什么要序列化

  1. 对象持久化:java对象是在内存里,当系统故障重启java对象相关信息随之消失,因此一些场景要求持久化java对象以保证重启jvm之后也能够恢复之前的状态。如游戏的存档,配置管理等
  2. 跨平台兼容性:不同的操作系统、硬件平台、编程语言所能处理的对象格式肯定各不相同,将对象序列化成标准的、大家都能处理的字节流格式,确保了跨平台、跨系统的兼容性。
  3. 方便网络传输:序列化之后的对象体积被压缩,减少了网络传输所需的带宽,同时需要传输对象的场景很多,如分布式系统的状态保持,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 要点

  1. 实现Serializable接口: Serializable接口起着标记作用,告诉JVM实现了该接口的对象是可以被序列化的,真正的序列化操作并不由其来完成。
  2. serialVersionUID:序列化ID,在反序列化时,会将字节流中的serialVersionUID与被序列化类中的serialVersionUID进行比较,一致则可以进行反序列化,不一致则抛出序列化版本不一致的异常。
  3. 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中,在父类、子类有相同属性的时候,先序列化子类对象,再父类对象,会导致子类对象的属性被父类覆盖