- 能够使用序列化流写出对象到文件
- 能够使用反序列化流读取文件到程序中
1. 概述
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据
、对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。
看图理解序列化
2. ObjectOutputStream
:序列化流
java.io.ObjectOutputStream
:序列化流,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
作用:把对象以流的方式写入到文件中保存
构造方法
public ObjectOutputStream(OutputStream out)
:创建写入指定OutputStream
的ObjectOutputStream
。
参数:OutputStream out
:字节输出流
代码如下:
FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
特有的成员方法
void writeObject(Object obj)
:将指定的对象写入ObjectOutputStream
。
使用步骤
- 创建
ObjectOutputStream
对象,构造方法中传递字节输出流 - 使用
ObjectOutputStream
对象中的方法writeObject
,把对象写入到文件中 - 释放资源
注意
文件是二进制字节存储的,无法直接查看
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\person.txt"));
oos.writeObject(new Person("钢铁侠",18)); // 文件是二进制字节存储的,无法直接查看
oos.close();
结果:(文件中内容)
/�� sr "cn.luis.demo04.ObjectStream.Person I ageL namet Ljava/lang/String;xp t 钢铁侠
前提
一个对象要想序列化,必须满足两个条件:
1. 该类必须实现java.io.Serializable
接口
Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。
2. 该类的所有属性必须是可序列化的
如果有一个属性不需要可序列化的,则该属性必须注明是【瞬态】的,使用transient
关键字修饰。
public class Employee implements java.io.Serializable {
public String name;
public String address;
public transient int age; // transient瞬态修饰成员,不会被序列化
public void addressCheck() {
System.out.println("Address check : " + name + " -- " + address);
}
}
方法演示
public final void writeObject (Object obj)
: 将指定的对象写出。
public class SerializeDemo{
public static void main(String [] args) {
Employee e = new Employee();
e.name = "zhangsan";
e.address = "beiqinglu";
e.age = 20;
try {
// 创建序列化流对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
// 写出对象
out.writeObject(e);
// 释放资源
out.close();
fileOut.close();
System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
} catch(IOException i) {
i.printStackTrace();
}
}
}
输出结果:
Serialized data is saved
3 ObjectInputStream
类:反序列化流
ObjectInputStream
反序列化流,将之前使用ObjectOutputStream
序列化的原始数据恢复为对象。
构造方法
public ObjectInputStream(InputStream in)
: 创建一个指定InputStream
的ObjectInputStream
。
参数:InputStream in
:字节输入流
特有的成员方法
public final Object readObject()
:从ObjectInputStream
读取对象。
使用步骤
- 创建
ObjectInputStream
对象,构造方法中传递字节输入流 - 使用
ObjectInputStream
对象中的方法readObject
读取保存对象的文件 - 释放资源
- 使用读取出来的对象(打印)
前提
- 必须存在类对应的class文件
- 类必须实现Serializable
3.5 反序列化操作
要点一
如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream
读取对象的方法:
public final Object readObject ()
: 读取一个对象。
注意
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException
异常。
代码:
public class DeserializeDemo {
public static void main(String [] args) {
Employee e = null;
try {
// 创建反序列化流
FileInputStream fileIn = new FileInputStream("employee.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);
// 读取一个对象
e = (Employee) in.readObject();
// 释放资源
in.close();
fileIn.close();
}catch(IOException i) {
// 捕获其他异常
i.printStackTrace();
return;
}catch(ClassNotFoundException c) {
// 捕获类找不到异常
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
// 无异常,直接打印输出
System.out.println("Name: " + e.name); // zhangsan
System.out.println("Address: " + e.address); // beiqinglu
System.out.println("age: " + e.age); // 0
}
}
要点二
JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常。发生这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
问题:
- 每次修改类的定义,都会给class文件生成一个新的序列号
解决方案:
- 无论是否对类的定义进行修改,都不重新生成新的序列号
- 可以手动给类添加一个序列号
格式:
- 在Serializable接口规定:可序列化类可以通过声明名为“serialVersionUID”的字段
- 该字段必须是静态(static)、最终(final)的long 型字段
- 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
static final long serialVersionUID = 1L; 常量不能改变
代码:
public class Employee implements java.io.Serializable {
// 加入序列版本号
private static final long serialVersionUID = 1L;
public String name;
public String address;
// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
public int eid;
public void addressCheck() {
System.out.println("Address check : " + name + " -- " + address);
}
}
案例:序列化集合
当我们想在文件中保存多个对象的时候,可以把多个对象存储到一个集合中,对集合进序列化和反序列化
- 将存有多个自定义对象的集合序列化操作,保存到
list.txt
文件中。 - 反序列化
list.txt
,并遍历集合,打印对象信息。
案例分析
- 把若干学生对象 ,保存到集合中。
- 把集合序列化。
- 反序列化读取时,只需要读取一次,转换为集合类型。
- 遍历集合,可以打印所有的学生信息
步骤
- 定义一个存储Person对象的
ArrayList
集合 - 往ArrayList集合中存储Person对象
- 创建一个序列化流ObjectOutputStream对象
- 使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
- 创建一个反序列化ObjectInputStream对象
- 使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
- 把Object类型的集合转换为ArrayList类型
- 遍历ArrayList集合
- 释放资源
案例实现
package cn.luis.oos;
import java.io.*;
import java.util.ArrayList;
/**
* @ClassName SerTest
* @Description TODO
* @Author L
* @Date 2020.06.27 11:36
* @Version 1.0
* @Remark
**/
public class SerTest {
public static void main(String[] args) throws Exception {
// 创建序列化所需的对象
Person student = new Person("老王", 18);
Person student2 = new Person("老张", 19);
Person student3 = new Person("老李", 30);
ArrayList<Person> arrayList = new ArrayList<>();
arrayList.add(student);
arrayList.add(student2);
arrayList.add(student3);
System.out.println(arrayList);
// 对象存储路径
String path = "E:\\File\\ideaProjects\\Test\\IO\\src\\cn\\luis\\resource\\a.txt";
// 序列化操作
OOS(arrayList, path);
// 反序列化
OIS(path);
}
/**
* 反序列化方法
*
* @throws Exception
*/
private static void OIS(String path) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(path));
// 读取对象,强转为ArrayList类型
ArrayList<Person> list = (ArrayList<Person>) ois.readObject();
System.out.println("反序列化成功,正在打印对象属性..");
for (int i = 0; i < list.size(); i++) {
Person s = list.get(i);
System.out.println(s.getName() + "--" + s.getAge());
}
} catch (IOException | ClassNotFoundException e) {
System.out.println("文件中没有序列化的对象...");
e.printStackTrace();
} finally {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 序列化方法
*
* @param arrayList
* @param path
* @throws Exception
*/
private static void OOS(ArrayList<Person> arrayList, String path) throws Exception {
// 创建 序列化流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
// 写出对象
oos.writeObject(arrayList);
System.out.println("序列化成功,点击文件查看...");
System.out.println("==========");
// 释放资源
oos.close();
}
}
结果:
[Person{name='老王', age=18}, Person{name='老张', age=19}, Person{name='老李', age=30}]
序列化成功,点击文件查看...
==========
反序列化成功,正在打印对象属性..
老王--18
老张--19
老李--30
Person实体类
package cn.luis.oos;
import java.io.Serializable;
public class Person implements Serializable{
// 无论此类是否修改,序列号都固定不变
private static final long serialVersionUID = 1L;
private String name;
//private static int age;
private /*transient*/ int age;
// public int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", 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;
}
}
瞬态和静态
当一个类的对象需要被序列化时,某些属性不需要被序列化时,这时不需要序列化的属性可以使
用关键字 transient 修饰。
只要被 transient 修饰了,序列化时这个属性就不会被序列化了。
同时静态修饰也不会被序列化,因为序列化是把对象数据进行持久化存储,而静态的属于类加
载时的数据,不会被序列化。
静态
-
静态优先于非静态加载到内存中(静态优先于对象进入到内存中)
-
序列化的是对象,静态与对象无关!
瞬态
public class Person implements Serializable{
// 无论此类是否修改,序列号都固定不变
private static final long serialVersionUID = 1L;
private String name;
private transient int age;
结果:
[Person{name='老王', age=18}, Person{name='老张', age=19}, Person{name='老李', age=30}]
序列化成功,点击文件查看...
==========
反序列化成功,正在打印对象属性..
老王--0
老张--0
老李--0