• Java序列化和反序列化


    Java序列化和反序列化

    序列化和反序列化的定义

    序列化:将对象数据转化为方便磁盘存储或网络传输的格式,即将对象转为二进制形式。
    反序列化:把磁盘存储或者网络传输的数据转化为对象的形式,即将二进制转化乘对象。
    使用场景:远程方法调用(remote method invoke)

    序列化算法的一般步骤:

    (1)将对象实例相关的类元数据输出。
    (2)递归地输出类的超类描述直到不再有超类。
    (3)类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
    (4)从上至下递归输出实例的数据

    Java原生序列方式

    实现Serialization接口

    Serialization是个标记接口,不包括任何方法,与ObjectOutputStream输出流和ObjectInputStream输入流联用

    • 序列化
      创建ObjectOutputStream对象,调用writeObject方法
    //Person.java
    import java.io.Serializable;
    
    public class Person implements Serializable{
        private String name;
        private int age;
    
        public Person(){}
    
        public Person(String name, int age){
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString(){
            return name + " " + age;
        }
    }
    
    //Transfer.java
    import java.io.FileOutputStream;
    import java.io.ObjectOutputStream;
    
    public class Transfer {
        public static void main(String[] args) {
            Person p = new Person("a", 13);
            try {
                ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("DataSet_1/1.txt"));
                out.writeObject(p);
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 反序列化
      创建ObjectInputStream对象,调用readObject方法
    //Person.java
    import java.io.Serializable;
    
    public class Person implements Serializable{
        private String name;
        private int age;
    
        public Person(){}  //反序列化不使用构造方法
    
        public Person(String name, int age){
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString(){
            return name + " " + age;
        }
    }
    
    //Recover.java
    import java.io.FileInputStream;
    import java.io.ObjectInputStream;
    
    public class Recover {
        public static void main(String[] args) {
            try {
                ObjectInputStream input = new ObjectInputStream(new FileInputStream("DataSet_1/1.txt"));
                Person p = (Person)input.readObject();
                System.out.println(p);
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }
        }
    }
    //a 13
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 成员是引用类型的序列化

    如果一个可序列化的类的成员不是基本类型,也不是String类型,那这个引用类型也必须是可序列化的;否则,会导致此类不能序列化。

    • 同一对象多次序列化

    同一对象多次序列化,只会序列化这个对象一次

    • 序列化ID的使用

    private static final long serialVersionUID = 1L;
    序列化ID决定着是否能够成功反序列化。java的序列化机制是通过判断运行时类的serialVersionUID来验证版本一致性的,在进行反序列化时,JVM会把传进来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。

    自定义序列化ID的情况:

    • 如果只是修改了方法,反序列化不容影响,则无需修改版本号;
    • 如果只是修改了静态变量,瞬态变量(transient修饰的变量),反序列化不受影响,无需修改版本号;
    • 如果修改了非瞬态变量,则可能导致反序列化失败。如果新类中实例变量的类型与序列化时类的类型不一致,则会反序列化失败,这时候需要更改serialVersionUID。如果只是新增了实例变量,则反序列化回来新增的是默认值;如果减少了实例变量,反序列化时会忽略掉减少的实例变量。
    • 默认序列化ID

    Java序列化机制会根据编译时的class自动生成一个serialVersionUID作为序列化版本比较,这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID。譬如,当我们编写一个类时,随着时间的推移,我们因为需求改动,需要在本地类中添加其他的字段,这个时候再反序列化时便会出现serialVersionUID不一致,导致反序列化失败。

    • 自定义序列化

    static,transient修饰的变量不能被序列化

    实现Externalizable接口

    另一种序列化的方式是实现Externalizable接口,强制自定义序列化,需要实现writeExternal、readExternal方法

    import java.io.Externalizable;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInput;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutput;
    import java.io.ObjectOutputStream;
    
    public class Student implements Externalizable{
        private String name;
        private int age;
    
        //必须有无参构造方法
        public Student(){}
    
        public Student(String name, int age){
            this.name = name;
            this.age = age;
        }
    
        public String toString() {
            return name + " " + age;
        }
    
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            // TODO Auto-generated method stub
            out.writeObject(name);
            out.writeInt(age);
        }
    
        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            // TODO Auto-generated method stub
            this.name = (String)in.readObject();
            this.age = in.readInt();
        }
    
        public static void main(String[] args) {
            try {
                ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("DataSet_1/Student.txt"));
                ObjectInputStream in = new ObjectInputStream(new FileInputStream("DataSet_1/Student.txt"));
                out.writeObject(new Student("bob", 11));
                Student s = (Student)in.readObject();
                System.out.println(s);
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    Externalizable接口不同于Serializable接口,实现此接口必须实现接口中的两个方法实现自定义序列化,这是强制性的;特别之处是必须提供pulic的无参构造器,因为在反序列化的时候需要反射创建对象。

    原生序列化的不足
    • 不支持跨语言调用
    • 性能差 (占用空间大、时间长)

    原生序列化的不足

    其他序列化框架

    • Kryo
    • Protobuf
    • hessian

    参考:
    https://zhuanlan.zhihu.com/p/449460157
    https://blog.csdn.net/qq_35890572/article/details/81630052

  • 相关阅读:
    山西电力市场日前价格预测【2023-09-28】
    Spring MVC处理用户请求的完整流程
    【HarmonyOS】元服务卡片展示动态数据,并定点更新卡片数据
    【SpringCloud】四、Spring Cloud Config
    Git下载安装及基本配置
    【编译原理】语法分析
    系统架构基础知识入门——架构初探
    程序假死怎么办
    Spring In Action 5 学习笔记 chapter8 RabbitMQ(AMQP)要点
    高危漏洞分析|CVE-2022-42920 Apache Commons BCEL 越界写漏洞
  • 原文地址:https://blog.csdn.net/weixin_40096160/article/details/126805114