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();
        }
    }
}
  • 反序列化
    创建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
  • 成员是引用类型的序列化

如果一个可序列化的类的成员不是基本类型,也不是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();
        }
    }

}

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

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

原生序列化的不足

其他序列化框架

  • Kryo
  • Protobuf
  • hessian

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值