首先需要明确的概念:
序列化
:将数据结构或对象转换成二进制字节流的过程反序列化
:将在序列化过程中所生成的二进制字节流的过程转换成数据结构或者对象的过程持久化
:将数据写入文件中长久保存的过程称之为持久化序列化主要目的
:是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
- 序列化反序列化实例:
1.创建一个学生类实现Serializable
接口:
public class Student implements Serializable {private String stuNum;private String stuName;//学生拥有多个老师 用集合装private List<String> teacherList;public Student() {}public Student(String stuNum, String stuName, List<String> teacherList) {this.stuNum = stuNum;this.stuName = stuName;this.teacherList = teacherList;}@Overridepublic String toString() {return "Student{" +"stuNum='" + stuNum + '\'' +", stuName='" + stuName + '\'' +", teacherList=" + teacherList +'}';}public String getStuNum() {return stuNum;}public void setStuNum(String stuNum) {this.stuNum = stuNum;}public String getStuName() {return stuName;}public void setStuName(String stuName) {this.stuName = stuName;}public List<String> getTeacherList() {return teacherList;}public void setTeacherList(List<String> teacherList) {this.teacherList = teacherList;}
}
2.创建SerializableUtil
工具类实现序列化反序列化方法
public class SerializableUtil {/*** 将对象序列化到指定文件中* @param obj* @param fileName* @throws IOException*/public static void mySerialize(Object obj, String fileName) throws IOException {OutputStream out = new FileOutputStream(fileName);//对象序列化反序列化的流ObjectOutputStream objOut = new ObjectOutputStream(out);//将指定对象写入ObjectOutStreamobjOut.writeObject(obj);//关闭流objOut.close();}/*** 从指定文件中反序列化对象* @param fileName* @return* @throws IOException* @throws ClassNotFoundException*/public static Object myDeserialize(String fileName) throws IOException, ClassNotFoundException {InputStream in = new FileInputStream(fileName);ObjectInputStream objIn = new ObjectInputStream(in);Object obj = objIn.readObject();return obj;}
}
3.SerializableTest
测试类,测试序列化反序列化实现
public class SerializableTest {public static void main(String[] args) {List<String> teacherList = new ArrayList<>();teacherList.add("王老师");teacherList.add("张老师");Student stu1 = new Student("1001", "张三", teacherList);System.out.println("原始对象:" + stu1);String fileName = "stu01.txt";try {//对象序列化SerializableUtil.mySerialize(stu1, fileName);System.out.println("序列化原始对象完成!OK!");//对象的反序列化Object obj = SerializableUtil.myDeserialize(fileName);//确定obj是Student类型if (obj instanceof Student) {Student stuNew = (Student) obj;System.out.println("反序列化之后的对象:" + stuNew);}} catch (Exception e) {e.printStackTrace();}}
}
4.测试结果:
生成stu01.txt
文件,将数据结构成功转为二进制字节流存入文件中:
部分属性序列化和反序列化的四种方法
Transient关键字修饰
private transient String stuNum;private transient String stuName;//学生拥有多个老师 用集合装private List<String> teacherList;
测试结果:
这里的
stuNum
和StuName
反序列化后就是默认值null
,因为经过了transient
修饰,阻止了属性的实例化,因此反序列结果中是null
。
对于不想进行序列化的变量,可以使用 transient
关键字修饰。
transient
关键字的作用是:让实例中的属性不再序列化;当对象被反序列化时,被 transient
修饰的属性不会被持久化和恢复。需要注意的一些点:
transient
关键字只能修饰属性,不能修饰方法和类。transient
关键字修饰的属性变量,在经过反序列化之后会是相应类型的默认值,例如String
类型反序列化后就是是null
,int
类型反序列化后就是0
。
Static关键字修饰
测试:
private String stuNum;private static String stuName;//学生拥有多个老师 用集合装private List<String> teacherList;
public static void main(String[] args) {List<String> teacherList = new ArrayList<>();teacherList.add("王老师");teacherList.add("张老师");Student stu1 = new Student("1001", "张三", teacherList);System.out.println("原始对象:" + stu1);String fileName = "stu01.txt";try {//对象序列化SerializableUtil.mySerialize(stu1, fileName);System.out.println("序列化原始对象完成!OK!");stu1.setStuName("张三他爹");//对象的反序列化Object obj = SerializableUtil.myDeserialize(fileName);//确定obj是Student类型if (obj instanceof Student) {Student stuNew = (Student) obj;System.out.println("反序列化之后的对象:" + stuNew);}} catch (Exception e) {e.printStackTrace();}}
测试结果:
这里通过用
static
关键字修饰,直接在完成初始序列化后set.stuName
可以改变学生名字,因为static
修改的属性变量不属于任何对象,是静态的,因此无论使用transient
与否,都不会被序列化。
修改默认方法writeObject和readObject
在Serializable
接口源码中有这样的说明
大概翻译一下writeObject
和readObject
测试:
public class Student implements Serializable {private String stuNum;private String stuName;private List<String> teacherList;private void writeObject(ObjectOutputStream objOut) throws IOException {System.out.println("writeObject-----------");objOut.writeObject(stuNum);objOut.writeObject(stuName);}private void readObject(ObjectInputStream objIn) throws IOException,ClassNotFoundException {System.out.println("readObject-----------");stuNum= (String) objIn.readObject();stuName= (String) objIn.readObject();}
}
测试结果:
因为在
writeObject
和readObject
方法中,只序列化和反序列化了stuNum
和stuName
,因此teacherList
是没有序列化反序列的,所以是String
的默认值null
Externalizable实现序列化反序列化和部分序列化
Externalizable
继承自Serializable
,使用Externalizable
接口需要实现readExternal
方法和writeExternal
方法来实现序列化和反序列化。
- 源码部分
- 代码实现
private String stuNum;private String stuName;//学生拥有多个老师 用集合装private List<String> teacherList;@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(stuNum);out.writeObject(stuName);}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {stuNum= (String) in.readObject();stuName= (String) in.readObject();}
测试结果:
同样是因为
writeExternal()
和readExternal()
只序列化和反序列化了stuNum
和stuName
,因此teacherList
是没有序列化反序列的,所以是String
的默认值null
Serializable 与 Externalizable
区别 | Serializable | Externalizable |
---|---|---|
实现复杂度 | 实现简单,Java对其有内建支持 | 实现复杂,由开发人员自己完成 |
执行效率 | 所有对象由Java统一保存,性能 | 开发人员决定哪个对象保存,可能造成速度提升 |
保存信息 | 保存时占用空间大 | 部分存储,可能造成空间减少 |
使用频率 | 高 | 偏低 |
关于序列化问题 Serializable
在修改公司项目的时候遇到这个问题,联想起之前做一篇博客。代码红线不贴了。
序列化的概念
序列化:把Java对象转换为字节序列的过程。
反序列化:把字节序列恢复为Java对象的过程。
序列化的方式
把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;(持久化对象)
在网络上传送对象的字节序列。(网络传输对象)
如何实现序列化
实现两个接口
Serializable 接口
Externalizable 接口
Serializable接口
一个对象想要被序列化,那么它的类就要实现此接口或者它的子接口。
这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。
不想序列化的字段可以使用transient修饰。
由于Serializable对象完全以它存储的二进制位为基础来构造,因此并不会调用任何构造函数,因此Serializable类无需默认构造函数,但是当Serializable类的父类没有实现Serializable接口时,反序列化过程会调用父类的默认构造函数,因此该父类必需有默认构造函数,否则会抛异常。
使用transient关键字阻止序列化虽然简单方便,但被它修饰的属性被完全隔离在序列化机制之外,导致了在反序列化时无法获取该属性的值,而通过在需要序列化的对象的Java类里加入writeObject()方法与readObject()方法可以控制如何序列化各属性,甚至完全不序列化某些属性或者加密序列化某些属性。
Externalizable 接口
它是Serializable接口的子类,用户要实现的writeExternal()和readExternal() 方法,用来决定如何序列化和反序列化。
因为序列化和反序列化方法需要自己实现,因此可以指定序列化哪些属性,而transient在这里无效。
对Externalizable对象反序列化时,会先调用类的无参构造方法,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor异常,因此Externalizable对象必须有默认构造函数,而且必需是public的。
serialVersionUID
serialVersionUID字段用来控制序列化的版本
一个对象数据,在反序列化过程中,如果序列化串中的serialVersionUID与当前对象值不同,则反序列化失败,否则成功。
如果serialVersionUID没有显式生成,系统就会自动生成一个。生成的输入有:类名、类及其属性修饰符、接口及接口顺序、属性、静态初始化、构造器。任何一项的改变都会导致serialVersionUID变化。
属性的变化都会导致自动生成的serialVersionUID发生变化。例如,对于对象A,我们生成序列化的S(A),然后修改A的属性,则此时A的serialVersionUID发生变化。反序列化时,S(A)与A的serialVersionUID不同,无法反序列化。会报序列号版本不一致的错误。