文章目录
- 零、本讲学习目标
- 一、对象序列化与反序列化
- (一)对象序列化与反序列化概念
- (二)对象序列化与反序列化示意图
- (三)实际开发中序列化和反序列化的场景
- (四)实现对象序列化的两种方法
- 1、实现Serializable接口
- 2、实现Externalizable接口
- (五)对象字节输出流
- 1、对象字节输出流API文档
- 2、对象字节输出流类结构图
- (六)对象字节输入流
- 1、对象字节输入流API文档
- 2、对象字节输入流类结构图
- 二、对象序列化与反序列化案例演示
- (一)创建学生类,实现序列化接口
- (二)创建测试学生类
- 1、测试序列化
- 2、测试反序列化
- 3、serialVersionUID作用
零、本讲学习目标
- 了解对象序列化与反序列化应用场景
- 掌握如何实现对象序列化与反序列化
一、对象序列化与反序列化
(一)对象序列化与反序列化概念
- **对象的序列化(Serialization)**是指将一个Java对象转换成一个I/O流中字节序列的过程。目的是为了将对象保存到磁盘中,或允许在网络中直接传输对象。
- 对象序列化可以使内存中的Java对象转换成与平台无关的二进制流,既可以将这种二进制流持久地保存在磁盘上,又可以通过网络将这种二进制流传输到另一个网络节点。
- 其他程序在获得了这种二进制流后,还可以将它恢复成原来的Java对象,将I/O流中的字节序列恢复为Java对象的过程被称之为反序列化(Deserialization)
(二)对象序列化与反序列化示意图
- 序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中
(三)实际开发中序列化和反序列化的场景
- 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
- 将对象存储到文件中的时候需要进行序列化,将对象从文件中读取出来需要进行反序列化。
- 将对象存储到缓存数据库(如 Redis)时需要用到序列化,将对象从缓存数据库中读取出来需要反序列化。
(四)实现对象序列化的两种方法
1、实现Serializable接口
- 系统自动存储必要信息
- Java内部支持,易实现,只需实现该接口即可,不需要其他代码支持
- 性能较差
- 容易实现,实际开发使用较多
2、实现Externalizable接口
- 由程序员决定存储的信息
- 接口中提供了两个空方法,实现该接口必须为两个方法提供实现
- 性能较好
- 编程复杂度大
(五)对象字节输出流
1、对象字节输出流API文档
- https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/ObjectOutputStream.html
2、对象字节输出流类结构图
- ObjectOutputStream类继承了OutputStream类,实现了ObjectOutput接口和ObjectStreamConstants接口
(六)对象字节输入流
1、对象字节输入流API文档
- https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/ObjectInputStream.html
2、对象字节输入流类结构图
- ObjectInputStream类继承了InputStream类,实现了ObjectInput接口和ObjectStreamConstants接口
二、对象序列化与反序列化案例演示
(一)创建学生类,实现序列化接口
- 在
c06.s03.p03
包里创建Student
类,实现Serializable
接口
package c06.s03.p03;
import java.io.Serializable;
/**
* 功能:学生类,实现序列化接口
* 作者:华卫
* 日期:2022年12月10日
*/
public class Student implements Serializable {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
(二)创建测试学生类
- 在
c06.s03.p03
包里创建TestStudent
类
1、测试序列化
- 创建testWrite()方法,测试序列化
- 使用了单元测试包
JUnit4
的注解符@Test
,要将JUnit4
添加到类路径
package c06.s03.p03;
import org.junit.Test;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
/**
* 功能:测试学生类
* 作者:华卫
* 日期:2022年12月10日
*/
public class TestStudent {
/**
* 序列化过程
*/
@Test
public void testWrite() throws Exception {
// 创建学生对象
Student student = new Student("howard", 18);
// 创建文件字节输出流
FileOutputStream fos = new FileOutputStream("src/c06/s03/p03/test1.txt");
// 创建对象字节输出流
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 写入学生对象数据
oos.writeObject(student);
// 关闭文件字节输出流
fos.close();
// 关闭对象字节输出流
oos.close();
}
}
-
运行
testWrite()
方法,查看结果
-
查看生成的输出文件test1.txt
-
说明:本来写入的对象只包含两项数据:
howard
,18
,可以看到序列化后的数据明显增多了,这是Java原生序列化的一个局限性。还有一点,看到的是乱码。 -
修改Student类,不实现Serializable接口
-
运行
testWrite()
方法就会抛出NotSerializableException
异常
-
修改Student类,实现Serializable接口
-
序列化id: 每个对象序列化时都会生成一个序列化
id
,假如不手动设置,那么会自动根据当前这个类生成一个序列化id
。当反序列化时,要根据序列化id
来操作,如果序列化id
和反序列化id
不同,那么反序列化就会失败。
2、测试反序列化
- 编写
testRead()
方法,测试反序列化
/**
* 反序列化过程
*/
@Test
public void testRead() throws Exception {
// 创建文件字节输入流
FileInputStream fis = new FileInputStream("src/c06/s03/p03/test1.txt");
// 创建对象字节输入流
ObjectInputStream ois = new ObjectInputStream(fis);
// 读取学生对象数据
Student student = (Student) ois.readObject();
// 输出学生信息
System.out.println(student);
// 关闭文件字节输入流
fis.close();
// 关闭对象字节输入流
ois.close();
}
-
运行
testRead()
方法,查看结果
-
确实将先前序列化保存在文件中的数据读取出来,然后反序列化成Java对象输出。
3、serialVersionUID作用
serialVersionUID
适用于Java的序列化机制。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID
与本地相应实体类的serialVersionUID
进行比较,如果相同就可以进行反序列化,否则就会出现异常。因此,为了在反序列化时确保序列化版本的兼容性,最好在每一个要序列化的类中加入private static final long serialVersionUID
的变量值,具体数值可自定义(默认是1L,系统还可以根据类名、接口名、成员方法及属性等生成的一个64位的哈希字段)。这样,某个对象被序列化之后,即使它所对应的类被修改了,该对象也依然可以被正确地反序列化。- 修改Student类,增加一个字段gender,添加对应的getter和setter,修改toString()方法
package c06.s03.p03;
import java.io.Serializable;
/**
* 功能:学生类,实现序列化接口
* 作者:华卫
* 日期:2022年12月10日
*/
public class Student implements Serializable {
private String name;
private int age;
private String gender;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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 String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
'}';
}
}
-
运行TestStudent类的
testRead()
方法,会抛出InvalidClassException
异常
-
异常信息:java.io.InvalidClassException: c06.s03.p03.Student; local class incompatible: stream classdesc serialVersionUID =
-6230387240686779692
, local class serialVersionUID =8464671208967089518
-
序列化id:local class serialVersionUID =
8464671208967089518
-
反序列化id:stream classdesc serialVersionUID =
-6230387240686779692
-
用错误提示信息中的反序列化
id
(-6230387240686779692
)去给Student类设置序列化id
-
再次运行
testRead()
方法,查看结果
-
修改
testWrite()
方法
-
运行
testWrite()
方法
-
运行
testRead()
方法,查看结果
-
查看
TestStudent
类源代码
package c06.s03.p03;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* 功能:测试学生类
* 作者:华卫
* 日期:2022年12月10日
*/
public class TestStudent {
/**
* 序列化过程
*/
@Test
public void testWrite() throws Exception {
// 创建学生对象
Student student = new Student("howard", 18);
student.setGender("男");
// 创建文件字节输出流
FileOutputStream fos = new FileOutputStream("src/c06/s03/p03/test1.txt");
// 创建对象字节输出流
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 写入学生对象数据
oos.writeObject(student);
// 关闭文件字节输出流
fos.close();
// 关闭对象字节输出流
oos.close();
}
/**
* 反序列化过程
*/
@Test
public void testRead() throws Exception {
// 创建文件字节输入流
FileInputStream fis = new FileInputStream("src/c06/s03/p03/test1.txt");
// 创建对象字节输入流
ObjectInputStream ois = new ObjectInputStream(fis);
// 读取学生对象数据
Student student = (Student) ois.readObject();
// 输出学生信息
System.out.println(student);
// 关闭文件字节输入流
fis.close();
// 关闭对象字节输入流
ois.close();
}
}