序列化流与反序列化流
- 1. 概述
- 2. 作用
- 3. 序列化流(对象操作字节输出流)
- 3.1 构造方法
- 3.2 成员方法
- 3.3 代码示例
- 4. 反序列化流(对象操作字节输入流)
- 4.1 构造方法
- 4.2 成员方法
- 4.3 代码示例
- 5. 细节
- 6. 练习
- 6.1 练习1:用对象流读写多个对象
- 7. 注意事项
文章中的部分照片来源于哔站
黑马程序员阿伟老师
处,仅用学习,无商用,侵权联系删除!
1. 概述
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据
、对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。看图理解序列化:
主要的序列化流和反序列化流有以下两种:
-
ObjectOutputStream(序列化流):
ObjectOutputStream
是用于将 Java 对象序列化为字节流的类。通过将对象写入ObjectOutputStream
,对象会被转换为字节序列,并可以保存到文件、网络等介质中。 -
ObjectInputStream(反序列化流):
ObjectInputStream
是用于从字节流中反序列化对象的类。通过读取ObjectInputStream
中的字节序列,可以将字节数据还原为原始的 Java 对象。
2. 作用
-
对象持久化存储:序列化流可以将 Java 对象转换为字节序列并保存到文件或数据库中,实现对象的持久化存储。这样可以在程序关闭后将对象数据保存在持久化介质中,下次程序运行时可以重新读取数据并还原为对象。
-
网络传输:通过序列化流,可以将对象转换为字节序列进行网络传输。在客户端和服务器之间传递对象数据时,可以使用序列化流将对象进行序列化后发送,接收方通过反序列化流还原对象。
-
跨平台数据交换:序列化流和反序列化流可以实现不同平台、不同语言之间对象数据的交换。在跨平台开发或不同系统之间数据传递时,可以使用序列化流将对象序列化为字节序列,从而实现数据的跨平台兼容性。
-
缓存对象:通过序列化流可以将对象存储在缓存中,提高程序性能。对象序列化后可以保存在内存或硬盘上,下次需要时直接反序列化而不用重新创建对象,节省了对象的创建时间。
3. 序列化流(对象操作字节输出流)
序列化流 /对象操作输出流
:可以把Java中的对象写到本地文件中
3.1 构造方法
构造方法 | 说明 |
---|---|
public ObjectOutputStream(OutputStream out) | 把基本流包装成高级流 |
- 构造方法:
public ObjectOutputStream(OutputStream out)
- 说明:将提供的
OutputStream
参数out
包装成ObjectOutputStream
,使得可以使用ObjectOutputStream
来将对象序列化并写入到这个输出流中。
- 说明:将提供的
3.2 成员方法
成员方法 | 说明 |
---|---|
public final void writeObject(Object obj) | 把对象序列化(写出)到文件中去 |
- 方法:
public final void writeObject(Object obj)
- 说明:将传入的对象
obj
进行序列化,并将序列化后的数据写出到文件中。
3.3 代码示例
- 代码示例
- 测试类
package text.IOStream.ObjectStream.ObjectStream01; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; /*序列化流/对象操作输出流 序列化流 /对象操作输出流:可以把lava中的对象写到本地文件中 | 构造方法 | 说明 | | ------------------------------------------- | -------------------- | | public ObjectOutputStream(OutputStream out) | 把基本流包装成高级流 | | 成员方法 | 说明 | | ----------------------------------------- | ---------------------------- | | public final void writeObject(Object obj) | 把对象序列化(写出)到文件中去 | 细节1: 使用对象输出流将对象保存到文件时会出现NotSerializableException异常 解决方案:需要让Javabean类实现Serializable接口 Serializable接口里面是没有抽象方法,是个标记型接口 一旦实现了这个接口,那么就表示当前的Student类可以被序列化 细节2: 序列化之后,修改Javabean类,再次反序列化,会出问题,会抛出InvalidClassException异常 解决方案:给JavaBean类添加serialVersionUID的(序列号、版本号) 添加序列号,java底层会在实现Serializable接口的类中生成个序列号,在输入文件时,也包含序列号 当修改了实现Serializable接口的类时,序列号也会更改,当输入文件的序列号与原来实现Serializable接口的类的序列号不一致时就会报错,因此我们需要手动给实现Serializable接口的类设置序列号 细节3: 如果一个对象中的某个成员变量的值不想被序列化,可以给该成员日安家transient关键字修饰 transient:瞬态关键字 作用:不会把当前的属性序列化到本地文件中 细节4: 序列化流写到文件中的数据是不能修改的,一旦修改就无法再次读回来了。 需求: 利用序列化流/对象操作输出流,把一个对象写到本地文件中 */ public class ObjectStream01 { public static void main(String[] args) throws IOException { //创建学生对象 Student stu = new Student("张三", 23, "南京"); //创建序列化流(对象操作输出流) ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\JavaCode\\code\\codeText01\\src\\text\\IOStream\\ObjectStream\\ObjectStream01\\student")); //输出本地文件 oos.writeObject(stu); //释放资源 oos.close(); } }
- 标准的Javabean类
package text.IOStream.ObjectStream.ObjectStream01; import java.io.Serial; import java.io.Serializable; //Serializable接口里面是没有抽象方法,是个标记型接口 //一旦实现了这个接口,那么就表示当前的student类可以被序列化 public class Student implements Serializable { //添加序列号,java底层会在实现Serializable接口的类中生成个序列号,在输入文件时,也包含序列号 //当修改了实现Serializable接口的类时,序列号也会更改,当输入文件的序列号与原来实现Serializable接口的类的序列号不一致时就会报错,因此我们需要手动给实现Serializable接口的类设置序列号 @Serial private static final long serialVersionUID = -5718005322642298180L; private String name; private int age; //transient:瞬态关键字 //作用:不会把当前的属性序列化到本地文件中 private transient String address; public Student() { } public Student(String name, int age, String address) { this.name = name; this.age = age; this.address = address; } /** * 获取 * * @return name */ public String getName() { return name; } /** * 设置 * * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * * @return age */ public int getAge() { return age; } /** * 设置 * * @param age */ public void setAge(int age) { this.age = age; } /** * 获取 * * @return address */ public String getAddress() { return address; } /** * 设置 * * @param address */ public void setAddress(String address) { this.address = address; } public String toString() { return "Student{ name = " + name + ", age = " + age + ", address = " + address + "}"; } }
- 输出结果
- student文件
- student文件
4. 反序列化流(对象操作字节输入流)
反序列化流 /对象操作输入流
:把序列化到本地文件中的对象,读取到程序中来
4.1 构造方法
构造方法 | 说明 |
---|---|
public ObjectInputStream(InputStream out) | 把基本流变成高级流 |
- 构造方法:
public ObjectInputStream(InputStream in)
- 说明:
ObjectInputStream
是一个对象输入流,用于从输入流in
中读取序列化的对象数据。
4.2 成员方法
成员方法 | 说明 |
---|---|
public Object readObject() | 可以把序列化到本地文件中的对象,读取到程序中来 |
- 方法:
public Object readObject()
- 说明:
readObject()
方法用于从对象输入流中读取序列化的对象数据,并将其还原为原始的对象。该方法会尝试将读取的数据转换为Object
类型,并需要进行类型转换为实际的对象类型。
4.3 代码示例
- 代码示例
- 需求:利用反序列化流/对象操作输入流,把文件中中的对象读到程序当中
- 测试类
package text.IOStream.ObjectStream.ObjectStream02; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; /*反序列化流 /对象操作输入流 反序列化流 /对象操作输入流:把序列化到本地文件中的对象,读取到程序中来 | 构造方法 | 说明 | | ----------------------------------------- | ------------------ | | public ObjectInputStream(InputStream out) | 把基本流变成高级流 | | 成员方法 | 说明 | | --------------------------- | ---------------------------------------------- | | public Object readObject () | 可以把序列化到本地文件中的对象,读取到程序中来 | 细节1: 使用对象输出流将对象保存到文件时会出现NotSerializableException异常 解决方案:需要让Javabean类实现Serializable接口 Serializable接口里面是没有抽象方法,是个标记型接口 一旦实现了这个接口,那么就表示当前的Student类可以被序列化 细节2: 序列化之后,修改Javabean类,再次反序列化,会出问题,会抛出InvalidClassException异常 解决方案:给JavaBean类添加serialVersionUID的(序列号、版本号) 添加序列号,java底层会在实现Serializable接口的类中生成个序列号,在输入文件时,也包含序列号 当修改了实现Serializable接口的类时,序列号也会更改,当输入文件的序列号与原来实现Serializable接口的类的序列号不一致时就会报错,因此我们需要手动给实现Serializable接口的类设置序列号 细节3: 如果一个对象中的某个成员变量的值不想被序列化,可以给该成员日安家transient关键字修饰 transient:瞬态关键字 作用:不会把当前的属性序列化到本地文件中 细节4: 序列化流写到文件中的数据是不能修改的,一旦修改就无法再次读回来了。 需求:利用反序列化流/对象操作输入流,把文件中中的对象读到程序当中 */ public class ObjectStream02 { public static void main(String[] args) throws IOException, ClassNotFoundException { //创建反序列化流(对象操作输入流) ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\JavaCode\\code\\codeText01\\src\\text\\IOStream\\ObjectStream\\ObjectStream01\\student")); //输入数据 Object student = ois.readObject(); //打印学生对象 System.out.println(student); //释放资源 ois.close(); } }
- 测试类
- 输出结果
5. 细节
-
细节1: 使用对象输出流将对象保存到文件时会出现NotSerializableException异常
-
解决方案:需要让Javabean类实现Serializable接口
-
Serializable接口里面是没有抽象方法,是个标记型接口
-
一旦实现了这个接口,那么就表示当前的Student类可以被序列化
-
-
-
细节2: 序列化之后,修改Javabean类,再次反序列化,会出问题,会抛出InvalidClassException异常
-
解决方案:给JavaBean类添加serialVersionUID的(序列号、版本号)
-
添加序列号,java底层会在实现Serializable接口的类中生成个序列号,在输入文件时,也包含序列号
-
当修改了实现Serializable接口的类时,序列号也会更改,当输入文件的序列号与原来实现Serializable接口的类的序列号不一致时就会报错,因此我们需要手动给实现Serializable接口的类设置序列号
-
-
-
细节3: 如果一个对象中的某个成员变量的值不想被序列化,可以给该成员日安家transient关键字修饰
-
transient:瞬态关键字
- 作用:不会把当前的属性序列化到本地文件中
-
-
细节4:序列化流写到文件中的数据是不能修改的,一旦修改就无法再次读回来了。
6. 练习
6.1 练习1:用对象流读写多个对象
-
需求
将多个自定义对象序列化到文件中,但是由于对象的个数不确定,反序列化该如何读取呢?
-
代码示例
- 序列化流(对象操作输出流)
package text.IOStream.ObjectStream.ObjectStream03; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.ArrayList; /*用对象流读写多个对象 需求: 将多个自定义对象序列化到文件中,但是由于对象的个数不确定,反序列化该如何读取呢? */ public class ObjectStream03 { public static void main(String[] args) throws IOException { //创建学生对象 Student s1 = new Student("张三", 23, "天津"); Student s2 = new Student("李四", 24, "南京"); Student s3 = new Student("王五", 25, "北京"); //创建集合,将学生对象添加进集合 ArrayList<Student> list = new ArrayList<>(); list.add(s1); list.add(s2); list.add(s3); //创建序列化流(对象操作输出流) ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\JavaCode\\code\\codeText01\\src\\text\\IOStream\\ObjectStream\\ObjectStream03\\a.txt")); //输出数据 oos.writeObject(list); //释放资源 oos.close(); } }
- 反序列化流(对象操作输入流)
package text.IOStream.ObjectStream.ObjectStream03; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.util.ArrayList; //反序列流(对象操作输入流) public class ObjectInput { public static void main(String[] args) throws IOException, ClassNotFoundException { //创建反序列化流对象(对象操作输入流) ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\JavaCode\\code\\codeText01\\src\\text\\IOStream\\ObjectStream\\ObjectStream03\\a.txt")); //输入数据 ArrayList<Student> list = (ArrayList<Student>) ois.readObject(); //遍历list集合 for (Student student : list) { System.out.println(student); } //释放资源 ois.close(); } }
- 标准的Javabean类
package text.IOStream.ObjectStream.ObjectStream03; import java.io.Serial; import java.io.Serializable; public class Student implements Serializable { @Serial private static final long serialVersionUID = -8803236034300730365L; private String name; private int age; private transient String address; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public Student(String name, int age, String address) { this.name = name; this.age = age; this.address = address; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } /** * 获取 * * @return name */ public String getName() { return name; } /** * 设置 * * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * * @return age */ public int getAge() { return age; } /** * 设置 * * @param age */ public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", address='" + address + '\'' + '}'; } }
-
输出结果
- a.txt
- 反序列化流(对象操作输入流)
- a.txt
7. 注意事项
-
版本控制:对象在被序列化后,如果类发生了变化(例如字段新增、删除、修改),在反序列化时可能会出现不兼容的情况。为了避免这种问题,可以在类中使用序列化版本号来进行版本控制。
-
transient关键字:在对象中使用 transient 关键字修饰的字段不会被序列化,这在某些情况下是很有用的,比如对于不需要序列化的敏感数据或临时计算结果。
-
对象引用:序列化后的对象图中可能包含重复的对象引用,这可能会导致数据的冗余性。在设计序列化对象时,可以考虑使用 writeObject() 和 readObject() 方法来处理对象引用,以减少序列化数据的冗余性。
-
Serializable接口:要使一个类可以被序列化,需要实现 Serializable 接口。这个接口没有任何方法,只是用来标记类具有可序列化的能力。在进行序列化和反序列化时,确保类的所有字段都是可序列化的,否则可能会导致异常。
-
所有序列化类的父类必须是可序列化的:如果一个类实现了 Serializable 接口,那么它的所有父类都应该是可序列化的,否则可能会导致序列化异常。