Java ---- 序列化
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,数组,Enum,Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。
对象的读写
Java类中对象的序列化工作是通过 ObjectOutputStream 和 ObjectInputStream 来完成的。
使用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 ---- 序列化相关推荐
- Java序列化的机制和原理
有关Java对象的序列化和反序列化也算是Java基础的一部分,下面对Java序列化的机制和原理进行一些介绍. Java序列化算法透析 Serialization(序列化)是一种将对象以一连串的字节描述 ...
- java基础(十)-----Java 序列化的高级认识
将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口,使用 ...
- java序列化和RMI
深入了解序列化"契约" 由于Java提供了良好的默认支持,实现基本的对象序列化是件比较简单的事.待序列化的Java类只需要实现Serializable接口即可.Serializab ...
- 什么是java序列化_什么是Java序列化?为什么序列化?序列化有哪些方式?
先普及一下,计算机中无法识别一个基本单元[字节]来表示,必须经过"翻译"才能让计算机理解人类的语言,这个翻译过程就是[编码],通常所说的字符转换为字节. ?有I/O的地方机就会涉及 ...
- java kryo_kryo序列化 - Java序列化期间的错误
我的应用程序有大量域对象,它们通过spring-session被序列化到Redis存储中.我试图使用Kryo(4.0.0)进行自动序列化,而不使对象明确可序列化.kryo序列化 - Java序列化期间 ...
- java 序列化慢_java原生序列化慢在哪里?
Java原生序列化和二进制序列化性能比较 序列化速度 package com.clq.netty.serializable; import java.io.ByteArrayOutputStream; ...
- Java序列化的作用和反序列化
1.序列化是干什么的? 简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来.虽然你可以用你自己的各种各样的方法来保存object states,但 ...
- Java序列化技术与Protobuff
前言: Java序列化是Java技术体系当中的一个重要议题,序列化的意义在于信息的交换和存储,通常会和io.持久化.rmi技术有关(eg:一些orm框架会要求持久化的对象类型实现Serializabl ...
- 深入理解JAVA序列化
2019独角兽企业重金招聘Python工程师标准>>> 如果你只知道实现 Serializable 接口的对象,可以序列化为本地文件.那你最好再阅读该篇文章,文章对序列化进行了更深一 ...
- Java 序列化的高级认识
这篇文章来自:http://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html 引言 将 Java 对象序列化为二进制文件的 Java ...
最新文章
- word被锁定无法编辑怎么解锁_Word论文里的公式怎么编辑?这4个小工具帮你一分钟搞定!...
- UA MATH523A 实分析3 积分理论例题 Fubini定理计算一元积分
- 反应式编程在微服务下的重生
- Server Develop (八) IOCP模型
- 2016年第9本:系统之美
- makefile语法_Makefile的语法
- C中取得数组的地址,赋值给数组结构的字段
- Mac版 matlab 安装 GAOT工具箱
- 封装Selenium2Library
- 阿里副总裁、达摩院自动驾驶负责人王刚离职!
- [题解]luogu_P3593_[NOIP2017]逛公园(最短路相关计数
- 用python对excel文件去重
- 向死而生的微信视频号,逆风翻盘的2020
- 【Linux】腾讯云服务器搭建环境
- NMOS与PMOS的区分及使用
- Linux命令:reboot
- 从淘宝服务器IP地址服务获取IP地址信息的方法
- 【回炉重造】——反射
- vue中iframe嵌套页面父子组件互相通信
- FFMPEG 抓取virtual-audio-capturer 数据