Java基础之对象序列化

Java基础之对象序列化

背景:

最近在准备一个用来做毕设的项目,emmmm,应该会是个博客,设想是前后端+爬虫吧,应该算个人网站吧,看看把学到的都集成上去,哎,就是玩,如果有大佬指点一下,当下做什么对工作比较有竞争力,那就万分感谢!

回到正题,今天在参考前后端分离的项目,看到作者将类或者对象(更合理)进行序列化了,所以在此研究一下。

概念:

先简单的说个大概,对象序列化,就是把一个对象变为二进制的数据流的一种方法,或者说,把内存中的实例化的对象保存(序列化)到本地,以便将来用的时候在反序列化读到内存中。

相关概念:

  • 序列化: 对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象状态的保存与重建。
  • 反序列化: 客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。

说完概念,我们说说为什么要这么干,这样有什么好处?

一:对象序列化可以实现分布式对象。

主要应用例如:RMI(即远程调用Remote Method Invocation)要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。

二:java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。

可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的"深复制",即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。

三:序列化可以将内存中的类写入文件或数据库中。

比如:将某个类序列化后存为文件,下次读取时只需将文件中的数据反序列化就可以将原先的类还原到内存中。也可以将类序列化为流数据进行传输。

总的来说就是将一个已经实例化的类转成文件存储,下次需要实例化的时候只要反序列化即可将类实例化到内存中并保留序列化时类中的所有变量和状态。

image-20210905205333026

图1

怎么知道该类的对象能不能被序列化?

  1. 对象所在的类必须实现Serializable接口(java.io)

首先定义序列化对象类:

定义:

 import java.io.Serializable;
 public class Member  implements Serializable {
     private String name;
     private int age;
     public Member(String name, int age) {
         this.name = name;
         this.age = age;
     }
     @Override
     public String toString() {
         return "Member{" +
                 "姓名='" + name + ''' +
                 ", 年龄=" + age +
                 '}';
     }
 }

序列化和反序化:

序列化和反序列化需要进行二进制存储格式上的统一,Serializable接口只是定义了某一个类的对象是否被允许序列化的支持,

实现序列化和反序列化的方式则是通过俩类实现:

  1. ObjectOutputStream
  2. ObjectInputStream

相关的方法可以通过Java API获取。

打个样:ObjectInputStream 构造方法

Modifier Constructor Description
protected ObjectInputStream() Provide a way for subclasses that are completely reimplementing ObjectInputStream to not have to allocate private data just used by this implementation of ObjectInputStream.
`` ObjectInputStream(InputStream in) Creates an ObjectInputStream that reads from the specified InputStream.

实现:

 import java.io.*;
 public class JavaIODenmo {
     //定义序列化的文件位置
     private static final File SAVE_FILE = new File("D:"+File.separator+"serializableText");
     public static void main(String[] args) throws Exception {
         saveObject(new Member("xbhog",18));
         System.out.println(LoadObject());
     }
     private static void saveObject(Object object) throws Exception {
         OutputStream fileOutputStream = new FileOutputStream(SAVE_FILE);
         ObjectOutputStream stream = new ObjectOutputStream(fileOutputStream);
         stream.writeObject(object);  //序列化
         stream.close();
     }
     private static Object LoadObject() throws Exception {
         /*实例化InputStream */
         InputStream inputStream = new FileInputStream(SAVE_FILE);
         ObjectInputStream stream = new ObjectInputStream(inputStream);
         Object o = stream.readObject();  //反序列化
         stream.close();
         return o;
     }
 }

先将对象实例化出来传入save中,以二进制方式作为序列化操作的存储到文件中,随后再通过Load将二进制数据读取并转为对象返回。

如果为了保证对象的高效传输,可以设置不重要的属性不进行序列化;

关键字:transient;

在原来的Member上设置:

 private transient String name;

再运行序列化程序:

 Member{姓名='null', 年龄=18}

可以看出,生成的序列化文件时不生成name的。

兼容问题:

粗略的说一下是关于serialVersionUID,它可以检测对象序列化和反序列化版本是否一致的问题。

建议是显示的定义,如果不定义,在代码编译的时候会自动生成。

给出源码中的注释详情,翻译版:

如果可序列化类未显式声明 serialVersionUID,则序列化运行时将根据该类的各个方面计算该类的默认 serialVersionUID 值,如 Java(TM) 对象序列化规范中所述。 但是,强烈建议所有可序列化类显式声明 serialVersionUID 值,因为默认的 serialVersionUID 计算对可能因编译器实现而异的类详细信息高度敏感,因此可能会在反序列化期间导致意外的InvalidClassException 。 因此,为了保证在不同的 Java 编译器实现中具有一致的 serialVersionUID 值,可序列化类必须声明一个显式的 serialVersionUID 值。 还强烈建议显式 serialVersionUID 声明尽可能使用private修饰符,因为此类声明仅适用于立即声明的类——serialVersionUID 字段作为继承成员没有用。 数组类无法声明显式的 serialVersionUID,因此它们始终具有默认的计算值,但数组类放弃了匹配 serialVersionUID 值的要求

参考文献:

序列化

Java入门到项目实践-李兴华

结束:

如果你看到这里或者正好对你有所帮助,希望能点个👍或者⭐感谢;

有错误的地方,欢迎在评论指出,作者看到会进行修改。

posted @ 2021-09-06 21:20  xbhog  阅读(329)  评论(0编辑  收藏  举报