关于java对象的序列化和反序列化很早就有所了解,但是一直对这个知识是一知半解。最近看了几篇文章,加上自己的思考,对于这个问题有了更深的理解,体会到了温故而知新

一. 序列化和反序列化

  • 序列化的多种方式
    java中有多种序列化方式,jdk序列化, json序列化, Hessian.....,我平时接触到了jdk序列化和json序列化。
    比如一个类实现Serializable接口,表示这个类可以被序列化,这种就是jdk自带的序列化; SpringMvc中使用了Jackson将后台的实体数据序列化成json数据,在前端进行展示,这种是json序列化
    序列化: 将对象转换为二进制流的过程
    反序列化: 将二进制流恢复为对象的过程
    通俗一点讲,就是把对象转换成另一种形式保存。即:将对象中的属性,属性值,属性类型....信息以另一种形式进行保存。
    把对象比成雪人,雪人可以拆分成一堆雪。雪更容易运输。后面我们可以再把雪堆成雪人,它的本质是没有改变的,其中包含的内容都是一样的。
  • 什么情况下需要序列化
    把内存中的java对象持久化到文件中或者数据库
    需要通过网络传输数据,如:客户端和服务器交互数据

image.png

二. java类序列化

  • 序列化常用的API
    java.io.ObjectOutputStream
    java.io.ObjectInputStream
    java.io.Serializable
    java.io.Externalizable
  • java.io.Serializable: 一个用于标记的空接口,没有方法或字段,实现了该接口表示,对象可以被序列化
  • java.io.Externalizable: 继承了Serializable,其中定义了两个抽象方法writeExternal()和readExternal()。如果实现了Externalizable接口,需要重写两个方法
  • java.io.ObjectOutputStream: 表示对象输出流,它的writeObject(Object obj)方法可以将指定的对象序列化为二进制流
  • java.io.ObjectInputStream: 表示对象输入流,它的Object readObject()方法,读取二进制流转换为一个对象
//定义一个类实现Serializable
public class Stud implements Serializable {
    
    private String name;
    
    private Integer age;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public Integer getAge() {
        return age;
    }
    
    public void setAge(Integer age) {
        this.age = age;
    }
     
    @Override
    public String toString() {
        return "Stud{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public static void main(String[] args) throws IOException {
        Stud stud = new Stud();
        stud.setName("test");
        stud.setAge(11);
        // 将对象序列化成二进制流保存到文件中,执行代码后可以在test.txt中看到数据,
        //只不过数据不是文本内容,看似乱码
        FileOutputStream fos = new FileOutputStream("D:\\test.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(stud);
    }    
public static void main(String[] args) throws IOException, ClassNotFoundException {       
    FileInputStream fis = new FileInputStream("D://test.txt");
    ObjectInputStream ois = new ObjectInputStream(fis);
    // 反序列为对象
    Stud stud =(Stud) ois.readObject();
    System.out.println(stud);
  }

需要注意的几个点:
1.如果一个对象要进行序列化,它的成员变量是引用类型,那么这个引用类型也要可序列化,否则程序会报错
2.父类实现了Serializable或者Externalizable,表示可以序列化;子类间接实现序列化接口,也可以被序列化
3.如果子类可以被序列化,但是父类没有实现序列化接口,那么从父类继承的成员变量是不会被序列化的
4.静态变量不会参与序列化;非静态变量使用transient修饰也不会参与序列化,反序列化时会按照其类型的默认值赋值

//定义一个类实现Externalizable,必须重写两个方法

public class Stud implements Externalizable {
    
    private static final long serialVersionUID = -6541501724579511412L;
    
    private String address;
    
    private String name;
    
    private Integer age;
    
    public String getAddress() {
        return address;
    }
    
    public void setAddress(String address) {
        this.address = address;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public Integer getAge() {
        return age;
    }
    
    public void setAge(Integer age) {
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Stud{" +
                "address='" + address + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        //方法中必须指定要参与序列化的属性
        out.writeInt(age);
        out.writeObject(name);
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        //方法中必须指定要参与反序列化的属性
        this.age = in.readInt();
        this.name =(String) in.readObject();
    }
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Stud stud = new Stud();
        stud.setAddress("llll");
        stud.setName("lisi");
        stud.setAge(11);
        
        FileOutputStream fos = new FileOutputStream("D://test.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(stud);
        
        FileInputStream fis = new FileInputStream("D://test.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        stud =(Stud) ois.readObject();
        System.out.println(stud);
        
    }

三.serialVersionUID
(1)serialVersionUID是一个标识符,主要用于对象的版本控制。
(2)如果java类已经实现了java.io.Serializable接口,那么默认会在类中添加一个serialVersionUID。但是在该类中添加、删除、修改字段都会生存新的serialVersionUID。这样导致序列化到文件中的serialVersionUID和修改后class中的serialVersionUID不一致,造成对象无法恢复。
(3)java序列化的过程依赖正确的serialVersionUID。无论是否改变类中的字段,serialVersionUID都应该保持不变。所以我们应该在类中显示的添加一个serialVersionUID。

四.Transient,static,final关键字
(1)该修饰符只适用于变量,不适用于方法和类。如果我们不想序列化特定变量以满足安全约束,那么我们将该变量声明为Transient。执行序列化时,JVM会忽略Transient变量的原始值,将默认值保存到文件中。因此Transient意味着不要序列化
(2)静态变量不属于对象状态的一部分,因此它不参与序列化。因此将在静态变量前添加Transient是无用的。
(3)final变量将直接通过值参与序列化。所以在final变量前添加Transient关键字不会产生任何影响。


forest
0 声望1 粉丝