Java IO流详解(七)----对象流(序列化与反序列化)
对象流的主要用作是对Java对象的序列化和反序列化的操作。在Java IO流中提供了两个对象流:ObjectInputStream和ObjectOutputStream,这两个类都属于字节流。其中ObjectOutputStream将Java对象以字节序列的形式写出到文件,实现对象的永久存储,它继承自OutputStream。ObjectInputStream是将之前使用ObjectOutputStream序列化的字节序列恢复为Java对象,它继承自InputStream。
序列化与反序列化
序列化 : 把Java对象转换成字节序列的过程。
反序列化:把序列化成字节序列的数据恢复为Java对象的过程。
为什么需要序列化?
①、把对象的字节序列永久地保存到硬盘上:对于一个存在JVM中的对象来说,其内部的状态只是保存在内存中。当JVM退出之后,内存资源也就被释放,Java对象的内部状态也就丢失了。而在很多情况下,对象内部状态是需要被持久化的,将运行中的对象状态保存下来(最直接的方式就是保存到文件系统中),在需要的时候可以还原,即使是在Java虚拟机退出的情况下。
②、在网络上传送对象的字节序列:对象序列化机制是Java内建的一种对象持久化方式,可以很容易实现在JVM中的活动对象与字节数组(流)之间进行转换,使用得Java对象可以被存储,可以被网络传输,在网络的一端将对象序列化成字节流,经过网络传输到网络的另一端,可以从字节流重新还原为Java虚拟机中的运行状态中的对象。
1、ObjectOutputStream类
ObjectOutputStream代表对象输出流,即序列化,将Java对象以字节序列的形式写出到文件,实现对象的永久存储。
它的 writeObject(Object obj) 方法可对指定的obj参数对象进行序列化。
首先需要明确的一点是:一个对象要想序列化,该对象必须要实现Serializable接口,否则会抛出NotSerializableException异常,Serializable接口是一个标记接口,它内部没有任何方法,和Cloneable接口是一样的。
下面定义一个Person类,并且实现Serializable接口:
package com.thr; import java.io.Serializable; /** * @author Administrator * @date 2020-02-28 * @desc Person对象 */ public class Person implements Serializable { private int id; private String name; private int age; public Person() { } public Person(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } //getter、setter、toString方法省略(自己测试需要加上)
}
序列化操作:
package com.thr; import java.io.*; /** * @author Administrator * @date 2020-02-28 * @desc 使用ObjectOutputStream序列化对象 */ public class ObjectOutputStreamTest { public static void main(String[] args) { //定义对象流 ObjectOutputStream oos = null; try { //创建对象流 oos = new ObjectOutputStream(new FileOutputStream("D:\\IO\\person.txt")); //序列化对象 oos.writeObject(new Person(10001,"张三",20)); oos.writeObject(new Person(10002,"李四",21)); //刷新缓冲区 oos.flush(); System.out.println("序列化成功..."); } catch (IOException e) { e.printStackTrace(); }finally { //释放资源 if (oos!=null){ try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
序列化之后打开的文件我们是看不懂的,因为它是字节序列文件,只有计算机懂。所以接下来需要将它反序列化成我们能看懂的。
2、ObjectInputStream类
ObjectOutputStream代表对象输入流,即反序列化,将之前使用ObjectOutputStream序列化的字节序列恢复为Java对象。
它的 readObject() 方法读取指定目录下的序列化对象。
反序列化操作:
package com.thr; import java.io.*; /** * @author Administrator * @date 2020-02-28 * @desc 使用ObjectInputStream反序列化对象 */ public class ObjectInputStreamTest { public static void main(String[] args) { //定义对象流 ObjectInputStream ois = null; try { //创建对象输入流对象 ois = new ObjectInputStream(new FileInputStream("D:\\IO\\person.txt")); //反序列化对象 Person person1 = (Person) ois.readObject(); Person person2 = (Person) ois.readObject(); System.out.println(person1); System.out.println(person2); System.out.println("反序列化成功..."); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { if (ois!=null){ try { ois.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
在使用ObjectInputStream反序列化时需要注意一点:
①、在完成序列化操作后,如果对序列化对象进行了修改,比如增加某个字段,那么我们再进行反序列化就会抛出InvalidClassException异常,这种情况叫不兼容问题。
解决的方法是:在对象中手动添加一个 serialVersionUID 字段,用来声明一个序列化版本号,之后再怎么添加属性也能进行反序列化,凡是实现Serializable接口的类都应该有一个表示序列化版本标识符的静态变量。
public class Person implements Serializable { //序列化版本号 private static final long serialVersionUID = 5687485987455L; private int id; private String name; private int age; //getter、setter、toString、构造方法省略(自己测试需要加上) }
注意:ObjectInputStream和ObjectOutputStream不能序列化transient修饰的成员变量。
Transient 关键字
transient修饰符仅适用于变量,不适用于方法和类。在序列化时,如果我们不想序列化特定变量以满足安全约束,那么我们应该将该变量声明为transient。执行序列化时,JVM会忽略transient变量的原始值并将默认值保存到文件中。因此,transient意味着不要序列化。
假如Person类中的age属性不需要序列化,在age属性上添加transient关键字。private transient int age;
package com.thr; import java.io.*; /** * @author Administrator * @date 2020-02-29 * @desc transient的使用 */ public class Test { public static void main(String[] args) { serialization(new Person(10001, "赵六", 20)); deserialization(); } //序列化 public static void serialization(Person person){ ObjectOutputStream oos = null; try { //创建输出对象流 oos = new ObjectOutputStream(new FileOutputStream("D:\\IO\\object.txt")); //序列化对象 oos.writeObject(person); oos.flush(); System.out.println("序列化成功..."); } catch (IOException e) { e.printStackTrace(); } finally { //释放资源 try { if (oos!=null){ oos.close(); } } catch (IOException e) { e.printStackTrace(); } } } //反序列化 public static void deserialization (){ ObjectInputStream ois = null; try { //创建输入对象流 ois = new ObjectInputStream(new FileInputStream("D:\\IO\\object.txt")); //反序列化对象 Person person = (Person) ois.readObject(); System.out.println(person); System.out.println("反序列化成功..."); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { ois.close(); } catch (IOException e) { e.printStackTrace(); } } } }
运行的结果为:Person{id=10001, name='赵六', age=0},可以发现,尽管age属性没有序列化,但是它是有默认值的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!