Java对象的序列化

  • Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。

  • 使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量

  • 简而言之,就是让对象想基本数据类型和字符串类型一样,通过输入输出字节流ObjectInputStream 和 ObjectOutputStream进行写和读操作。

Java序列化的应用场景

  • 当你想把的内存中的对象状态保存到一个文件中或者数据库中时候

  • 当你想用套接字在网络上传送对象的时候

  • 当你想通过RMI传输对象的时候

如何对Java对象进行序列化与反序列化

在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。

import java.io.*;
import java.util.*;class User implements Serializable {private String name;private int age;private Date birthday;private transient String gender;private static int test =1;private static final long serialVersionUID = -6849794470754667710L;public User() {System.out.println("none-arg constructor");}public User(String name, Integer age,Date birthday,String gender) {System.out.println("arg constructor");this.name = name;this.age = age;this.birthday = birthday;this.gender = gender;}public void setTest(int Newtest) {this.test = Newtest;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", gender=" + gender +", birthday=" + birthday +", testStatic="+test+'}'+" "+super.toString();}
}public class SerializableDemo {public static void main(String[] args) throws Exception {//Initializes The ObjectUser user = new User("qiuyu",23,new Date(),"male");System.out.println(user);user.setTest(10);System.out.println(user);//Write Obj to FileObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempFile"));out.writeObject(user);out.close();//Read Obj from FileObjectInputStream in = new ObjectInputStream(new FileInputStream("tempFile"));User newUser = (User) in.readObject();System.out.println(newUser);in.close();}
}

此时的输出为:

arg constructor
User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 20:38:57 GMT+08:00 2017, testStatic=10} User@326de728
User{name='qiuyu', age=23, gender=null, birthday=Tue Nov 14 20:38:57 GMT+08:00 2017, testStatic=10} User@4883b407
  • 此时必须注意的是,当重新读取被保存的User对象时,并没有调用User的任何构造器,看起来就像是直接使用字节将User对象还原出来的。

  • 当User对象被保存到tempfile文件中之后,我们可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的CLASSPATH中包含有User.class,否则会抛出ClassNotFoundException。

Q:之前不是说序列化不保存静态变量么,为什么这里的静态变量进行了传递,都变成了10呢?
A:因为此时User.class已经被加载进了内存中,且将static变量test从1更改为了10。当我们恢复对象时,会直接获取当前static的变量test的值,所以为10。

Q:但如果我们的恢复操作在另一个文件中进行,结果会怎么样呢?

import java.io.FileInputStream;
import java.io.ObjectInputStream;public class Recovering {public static void main(String[] args) throws Exception{//Read Obj from FileObjectInputStream in = new ObjectInputStream(new FileInputStream("tempFile"));User newUser = (User) in.readObject();System.out.println(newUser);in.close();}
}

输出结果为:

User{name='qiuyu', age=23, gender=null, birthday=Tue Nov 14 20:38:57 GMT+08:00 2017, testStatic=1} User@6442b0a6

A:因为在运行此代码时,User.class会被加载进内存,然后执行初始化,将被初始化为1,因此test变量会被恢复为1。注意区分上面两种情况,不要因为是在本地进行测试,就认为static会被序列化,同时要了解Java运行时,内存加载的机制。

基本知识点

Serializable接口

  • 对于任何需要被序列化的对象,都必须要实现接口Serializable,它只是一个标识接口本身没有任何成员,只是用来标识说明当前的实现类的对象可以被序列化.

  • 如果父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口。

  • 如果被写对象的类型是String数组EnumSerializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。

对象的读写

  • Java类中对象的序列化工作是通过 ObjectOutputStreamObjectInputStream 来完成的。

  • 使用readObject()writeObject()方法对对象进行读写操作;

  • 对于基本类型,可以使用readInt()writeInt() , readDouble()writeDouble()等类似的接口进行读写。

序列化机制

  • 如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化。

  • 在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。为此需要为某个字段声明为transient,那么序列化机制就会忽略被transient修饰的字段。transient的引用变量会以null返回,基本数据类型会以相应的默认值返回。(例如:引用类型没有实现Serializable,或者动态数据只可以在执行时求出而不能或不必存储)

writeObject()与readObject()

  • 对于上被声明为transient的字段gender,除了将transient关键字去掉之外,是否还有其它方法能使它再次可被序列化?方法之一就是在User类中添加两个方法:writeObject()与readObject(),如下所示:

private void writeObject(ObjectOutputStream out) throws IOException {out.defaultWriteObject();out.writeUTF(gender);}private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject();gender = in.readUTF();}
  • 在writeObject()方法中会先调用ObjectOutputStream中的defaultWriteObject()方法,该方法会执行默认的序列化机制,此时会忽略掉gender字段。

  • 然后再调用writeUtf()方法显示地将gender字段写入到ObjectOutputStream中。readObject()的作用则是针对对象的读取,其原理与writeObject()方法相同。

Q: writeObject()与readObject()都是private方法,那么它们是如何被调用的呢?
A: 毫无疑问,是使用反射。(注意这不是继承接口的方法,因为接口类的方法都是public的,而这里的方法是private的)

Externalizable接口

  • 无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。JDK中提供了另一个序列化接口Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。

import java.io.*;
import java.util.*;class UserExtern implements Externalizable {private String name;private int age;private Date birthday;private transient String gender;private static int test =1;private static final long serialVersionUID = -6849794470754667710L;public UserExtern() {System.out.println("none-arg constructor");}public UserExtern(String name, Integer age,Date birthday,String gender) {System.out.println("arg constructor");this.name = name;this.age = age;this.birthday = birthday;this.gender = gender;}private void writeObject(ObjectOutputStream out) throws IOException {out.defaultWriteObject();out.writeUTF(gender);}private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject();gender = in.readUTF();}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeUTF(name);out.writeInt(age);out.writeObject(birthday);out.writeUTF(gender);}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {name = in.readUTF();age = in.readInt();birthday = (Date) in.readObject();gender = in.readUTF();}public void setTest(int Newtest) {this.test = Newtest;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", gender=" + gender +", birthday=" + birthday +", testStatic="+test+'}'+" "+super.toString();}
}public class ExternalizableDemo {public static void main(String[] args) throws Exception {UserExtern userExtern = new UserExtern("qiuyu",23,new Date(),"male");System.out.println(userExtern);ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("external"));out.writeObject(userExtern);out.close();ObjectInputStream in = new ObjectInputStream(new FileInputStream("external"));UserExtern userExtern1 = (UserExtern)in.readObject();System.out.println(userExtern1)}
}

输出结果为:

arg constructor
User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:34:25 GMT+08:00 2017, testStatic=1} UserExtern@25618e91
none-arg constructor
User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:34:25 GMT+08:00 2017, testStatic=1} UserExtern@604ed9f0
  • Externalizable继承于Serializable,当使用该接口时,序列化的细节需要由程序员去完成writeExternal()readExternal()方法的具体细节,以及哪些状态需要进行序列化。

  • 另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此序列化过程中UserExtern的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public

注意事项

  • 读取对象的顺序必须与写入的顺序相同

  • 如果有不能被序列化的对象,执行期间就会抛出NotSerializableException异常

  • 序列化时,只对对象的状态进行保存,而不管对象的方法

  • 静态变量不会被序列化,因为所有的对象共享同一份静态变量的值

  • 如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存还原,而且会是递归的方式(对象网)。(序列化程序会将对象版图上的所有东西储存下来,这样才能让该对象恢复到原来的状态)

  • 如果子类实现Serializable接口而父类未实现时,父类不会被序列化,但此时父类必须有个无参构造方法,否则会抛InvalidClassException异常;因为反序列化时会恢复原有子对象的状态,而父类的成员变量也是原有子对象的一部分。由于父类没有实现序列化接口,即使没有显示调用,也会默认执行父类的无参构造函数使变量初始化;

深入理解

序列化ID的问题

  • serialVersionUID适用于JAVA的序列化机制。简单来说,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。

  • 在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。

序列化存储规则

  • Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用;

  • 序列化到同一个文件时,如第二次修改了相同对象属性值再次保存时候,虚拟机根据引用关系知道已经有一个相同对象已经写入文件,因此只保存第二次写的引用,所以读取时,都是第一次保存的对象,第二次进行的修改将无效。

    public static void main(String[] args) throws Exception {//Initializes The ObjectUser user = new User("qiuyu",23,new Date(),"male");user.setTest(10);System.out.println(user);//Write Obj to FileObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempFile"));out.writeObject(user);user.setAge(25);System.out.println(user);out.writeObject(user);out.close();//Read Obj from FileObjectInputStream in = new ObjectInputStream(new FileInputStream("tempFile"));User newUser = (User) in.readObject();User newUser1 = (User) in.readObject();System.out.println(newUser);System.out.println(newUser1);in.close();}

输出结果: 注意观察age的值

User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@326de728
User{name='qiuyu', age=25, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@326de728
User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@4883b407
User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@4883b407

多次序列化的问题

  • 在一次的序列化的过程中,ObjectOutputStream 会在文件开始的地方写入一个 Header的信息到文件中。于是在多次序列化的过程中就会继续在文件末尾(本次序列化的开头)写入 Header 的信息,这时如果进行反序列化的对象的时候会报错:java.io.StreamCorruptedException: invalid type code: AC

Java ---- 序列化相关推荐

  1. Java序列化的机制和原理

    有关Java对象的序列化和反序列化也算是Java基础的一部分,下面对Java序列化的机制和原理进行一些介绍. Java序列化算法透析 Serialization(序列化)是一种将对象以一连串的字节描述 ...

  2. java基础(十)-----Java 序列化的高级认识

    将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口,使用 ...

  3. java序列化和RMI

    深入了解序列化"契约" 由于Java提供了良好的默认支持,实现基本的对象序列化是件比较简单的事.待序列化的Java类只需要实现Serializable接口即可.Serializab ...

  4. 什么是java序列化_什么是Java序列化?为什么序列化?序列化有哪些方式?

    先普及一下,计算机中无法识别一个基本单元[字节]来表示,必须经过"翻译"才能让计算机理解人类的语言,这个翻译过程就是[编码],通常所说的字符转换为字节. ?有I/O的地方机就会涉及 ...

  5. java kryo_kryo序列化 - Java序列化期间的错误

    我的应用程序有大量域对象,它们通过spring-session被序列化到Redis存储中.我试图使用Kryo(4.0.0)进行自动序列化,而不使对象明确可序列化.kryo序列化 - Java序列化期间 ...

  6. java 序列化慢_java原生序列化慢在哪里?

    Java原生序列化和二进制序列化性能比较 序列化速度 package com.clq.netty.serializable; import java.io.ByteArrayOutputStream; ...

  7. Java序列化的作用和反序列化

    1.序列化是干什么的? 简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来.虽然你可以用你自己的各种各样的方法来保存object states,但 ...

  8. Java序列化技术与Protobuff

    前言: Java序列化是Java技术体系当中的一个重要议题,序列化的意义在于信息的交换和存储,通常会和io.持久化.rmi技术有关(eg:一些orm框架会要求持久化的对象类型实现Serializabl ...

  9. 深入理解JAVA序列化

    2019独角兽企业重金招聘Python工程师标准>>> 如果你只知道实现 Serializable 接口的对象,可以序列化为本地文件.那你最好再阅读该篇文章,文章对序列化进行了更深一 ...

  10. Java 序列化的高级认识

    这篇文章来自:http://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html 引言 将 Java 对象序列化为二进制文件的 Java ...

最新文章

  1. word被锁定无法编辑怎么解锁_Word论文里的公式怎么编辑?这4个小工具帮你一分钟搞定!...
  2. UA MATH523A 实分析3 积分理论例题 Fubini定理计算一元积分
  3. 反应式编程在微服务下的重生
  4. Server Develop (八) IOCP模型
  5. 2016年第9本:系统之美
  6. makefile语法_Makefile的语法
  7. C中取得数组的地址,赋值给数组结构的字段
  8. Mac版 matlab 安装 GAOT工具箱
  9. 封装Selenium2Library
  10. 阿里副总裁、达摩院自动驾驶负责人王刚离职!
  11. [题解]luogu_P3593_[NOIP2017]逛公园(最短路相关计数
  12. 用python对excel文件去重
  13. 向死而生的微信视频号,逆风翻盘的2020
  14. 【Linux】腾讯云服务器搭建环境
  15. NMOS与PMOS的区分及使用
  16. Linux命令:reboot
  17. 从淘宝服务器IP地址服务获取IP地址信息的方法
  18. 【回炉重造】——反射
  19. vue中iframe嵌套页面父子组件互相通信
  20. FFMPEG 抓取virtual-audio-capturer 数据

热门文章

  1. Webfrom --图片验证码
  2. centos6.0下ffmpeg的安装编译经历
  3. 使用C#调用Python脚本,带参数列表
  4. 降维打击:这款GAN可以让真人「二次元化」
  5. 【以太坊】ubuntu安装以太坊ethereum的测试网络ropsten-net以及雷电网络raiden-network环境...
  6. MySQL多实例的安装
  7. HDU 4391 Paint The Wall 段树(水
  8. python 使用记录
  9. ibatis代码生成器
  10. linux 阅读日志命令 tail