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

桃扇骨 2022-05-30 02:46 187阅读 0赞

文章目录

在远程方法调用 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 的影响。

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

    //声明属性

    1. private String userName;
    2. private String password;
    3. private int age;
    4. private int age2;
    5. //get set方法略
    6. //这两个方法无需显式调用,但是哪些属性需要序列化,则需要单独指明
    7. @Override
    8. public void writeExternal(ObjectOutput out) throws IOException {
    9. out.writeUTF(password);
    10. out.writeUTF(userName);
    11. out.writeInt(age);
    12. out.writeInt(age2);
    13. }
    14. @Override
    15. public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    16. userName=in.readUTF();
    17. password=in.readUTF();
    18. age=in.readInt();
    19. age2=in.readInt();
    20. }
  • 序列号的生成

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

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

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

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

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

  • 序列号的意义

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

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

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

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

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

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

    package common;

    1. import java.io.Serializable;
    2. public class Model implements Serializable{
    3. private static final long serialVersionUID = -3265803653258922833L;
    4. private String userName;
    5. private String password;
    6. private transient int age;
    7. private int age2;
    8. private transient String other;//transient 表示该字段不参与序列化
    9. //get\set 方法 略
    10. //如果 implements Externalizable ,需在下面的方法中指明需序列化个反序列化字段
    11. //序列化字段
    12. /*@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 {

    1. //文件路径和名字
    2. private static String filePath="D://a.txt";
  1. public static void main(String[] args) {
  2. Model m=new Model();
  3. m.setUserName("jecket");
  4. m.setPassword("123456");
  5. m.setOther("other");
  6. System.out.println(ToStringBuilder.reflectionToString(m));
  7. //写到文件
  8. jdkSerializableWrite(filePath,m);
  9. //从文件读取
  10. Model m2=jdkSerializableRead(filePath);
  11. System.out.println(ToStringBuilder.reflectionToString(m2));
  12. }
  13. /** * 将对象序列化写入 txt 文件 * @param <T> * @param t */
  14. public static <T> void jdkSerializableWrite(String filePath,T t){
  15. try {
  16. ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(filePath));
  17. oos.writeObject(t);
  18. oos.close();
  19. } catch (FileNotFoundException e) {
  20. // TODO Auto-generated catch block
  21. e.printStackTrace();
  22. } catch (IOException e) {
  23. // TODO Auto-generated catch block
  24. e.printStackTrace();
  25. }
  26. }
  27. /** * 读取 序列化对象 从txt文件 * @param <T> * @param filePath * @return */
  28. public static <T> T jdkSerializableRead(String filePath) {
  29. try {
  30. @SuppressWarnings("resource")
  31. ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filePath));
  32. return (T) ois.readObject();
  33. } catch (FileNotFoundException e) {
  34. // TODO Auto-generated catch block
  35. e.printStackTrace();
  36. } catch (IOException e) {
  37. // TODO Auto-generated catch block
  38. e.printStackTrace();
  39. } catch (ClassNotFoundException e) {
  40. // TODO Auto-generated catch block
  41. e.printStackTrace();
  42. }
  43. return null;
  44. }
  45. }

发表评论

表情:
评论列表 (有 0 条评论,187人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java对象序列序列

    一、序列化和反序列化的概念   把对象转换为字节序列的过程称为对象的序列化。   把字节序列恢复为对象的过程称为对象的反序列化。   对象的序列化主要有两种用途:

    相关 Java对象序列序列

    一、序列化和反序列化的概念   把对象转换为字节序列的过程称为对象的序列化。   把字节序列恢复为对象的过程称为对象的反序列化。   对象的序列化主要有两种用途: