(二)远程服务:Java 对象序列化和反序列化

时间:2022-02-20 20:53:29

在远程方法调用 RMI 学习的过程中,涉及到一个概念,序列化,本文进行详述。

  • Java 对象的序列化和反序列化 的两种应用场景

    有时候需要将 Java 对象保存永久保存,比如保存到文件中,过程:Java 对象 -> IO 对象流 -> 写入文件 -> 字符串。当我们需要将文档中的字符串恢复为 Java 对象的时候,需要相反的过程:字符串 -> 读文件 -> IO 流 -> (强制)转化为 Java 对象
    还有一种场景,在网络通信中,对象要先变为IO流经过网络传输之后,再被还原为Java对象。过程:对象 -> 写IO -> 读IO -> 对象

  • 序列化是一个处理对象信息的过程

    序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。
    反序列化是将传输信息恢复为对象的过程。

  • Java 对象被序列化的两种方式

    方式一:Java 对象所属的类需要直接或者间接实现 Serializable 接口。
    方式二:Java 对象所属的类需要直接或者间接实现 Externalizable接口。这里需要注意,如果通过继承实现Externalizable接口,必须覆盖重写 writeExternal 和 readExternal 方法,否则子类新增属性无法参与序列化。

  • Serializable 和 Externalizable 的区别

    Serializable 可以是间接或者直接实现,所在类的属性都会被序列化。
    Serializable 的思路是,如果没有特殊,本类以及子类的属性将参与序列化。
    Externalizable 也可以是间接实现或者直接实现,但是其参与序列化和反序列化的属相都需要通过重写方法去指定。

  • transient
    实现 Serializable 的类 ,其属性默认都会参与序列化和反序列化,如果不想某个属性参与序列化,增加 transient 修饰即可。但是 实现 Externalizable 的类不会受transient 的影响。

    private transient String other;//transient 表示该字段不参与序列化
  • Externalizable 需要覆盖重写的 writeExternal 和 readExternal 方法举例


    //声明属性
    private String userName;
    private String password;
    private int age;
    private int age2;   

    //get set方法略

    //这两个方法无需显式调用,但是哪些属性需要序列化,则需要单独指明 
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(password);
        out.writeUTF(userName);
        out.writeInt(age);
        out.writeInt(age2);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        userName=in.readUTF();
        password=in.readUTF();
        age=in.readInt();
        age2=in.readInt();
    }
  • 序列号的生成

    Java 的序列化和反序列化工作是由 JVM 完成的,在序列化和反序列化的过程中,JVM 会根据类的路径、名字、内容生成一个 long 类型的数字串(当然你也可以自己指定),这个数字串被称为序列号。

    private static final long serialVersionUID = -5635658486326609381L;
  • 序列号的生成和 JVM 的版本有关系吗?

    根据我的实验,对于同一个路径下,同一个类,在内容不变的情况下,JVM 生成的序列号是一致的。测试 jdk 版本有 jdk1.6、jdk1.7、jdk1.8。

  • 序列号是允许不显式表述的

    序列号是可以不写出来的,但是要求序列化参照的类和反序列化参照的类都不写才可以。

  • 序列号的意义

    由于序列号的生成是和 类的路径、名字、内容 有关,这意味着如果你的类的内容有修改,JVM 自动生成的序列号就会有变动,如果你在类中指定了序列号,JVM 就会以你指定的为准。这样做是有好处的,可以提醒你远程交互中两端的类的版本不一致。但是这也有弊端,对于远程调用来说,如果一方
    实体类有变动,那么双方生成的序列号可能就不一样。所以序列号是为了保证序列化和反序列化双方的一致性。

  • 一定要显式的指定序列号

    由于我们无法确保类的内容不会更改,所以一定要写序列号,这个是可以自定义的,比如写成1L

  • Java 对象序列化会影响性能且需要平台一致性

    显然序列化会影响性能,而且需要同为 Java 平台。

  • 序列化应用的简单测试——以对象保存到 txt 中为例

    • 实体类
    package common;
    import java.io.Serializable;
    public class Model implements Serializable{
    private static final long serialVersionUID = -3265803653258922833L;

    private String userName;
    private String password;
    private transient int age;
    private int age2;
    private transient String other;//transient 表示该字段不参与序列化
    //get\set 方法 略

    //如果 implements Externalizable ,需在下面的方法中指明需序列化个反序列化字段
    //序列化字段
    /*@Override public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(password); out.writeUTF(userName); out.writeInt(age); out.writeInt(age2); } //反序列化字段 @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { userName=in.readUTF(); password=in.readUTF(); age=in.readInt(); age2=in.readInt(); }*/

}
  • 测试方法
    package common;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test {

    //文件路径和名字
    private static String filePath="D://a.txt";


    public static void main(String[] args) {
        Model m=new Model();
        m.setUserName("jecket");
        m.setPassword("123456");
        m.setOther("other");

        //写到文件
        write(m);

        //从文件读取
        read();
    }

    //将对象保存到 文件
    public static void write(Model m){
         try {
            ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(filePath));
            oos.writeObject(m);
            oos.close();

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  
    }


    //从对象中读取 文件
    public static void read(){
        try {
            @SuppressWarnings("resource")
            ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filePath));
            Model m=(Model)ois.readObject();
            System.out.println(m.getUserName());

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  
    }

}