Java 中序列化流的学习
序列化和反序列化的概述
Java提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了ー个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存的数据信息,都可以用来在内存中创建对象。
ObjectOutputStream类
java.io.ObjectOutputStream类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
构造方法
复制ObjectOutputStream(OutputStream out) // 创建写入指定OutputStream的ObjectOutputStream。 参数: OutputStream out:字节输出流
特有的成员方法
复制public final void writeObject(Object obj) // 将指定的对象写入ObjectOutputStream。
举例:使用ObjectOutputStream类,将Java对象的原始数据类型写出到文件。
使用步骤:
复制1.创建ObjectOutputStream对象,构造方法中传递字节输出流。 2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中。 3.释放资源。
定义一个类,存储这个类对象:
在定义之前,需要知道:
类通过实现java.io.Serializable接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。没有实现这个接口,进行序列化或反序列化,会抛出NotSerializableException异常。
因此,我们需要将Java对象的原始数据写出到文件,那么该对象类必须实现Serializable接口。
复制import java.io.Serializable; public class Person implements Serializable { private String name; private 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; } }
代码实现:
复制import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class Demo01ObjectOutputStream { public static void main(String[] args) throws IOException { method(); } private static void method() throws IOException { // 创建ObjectOutputStream对象,构造方法中传递字节输出流。 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/Users/liyihua/IdeaProjects/Study/src/view/study/demo36/person.txt")); // 使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中。 oos.writeObject(new Person("程序猿", 22)); oos.writeObject(new Person("攻城狮", 25)); // 释放资源。 oos.close(); } }
运行后,生成一个person.txt文件,文件内容如下:
里面存储了Person对象的数据信息。
ObjectInputStream类
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
构造方法
复制ObjectInputStream(InputStream in) // 创建从指定的InputStream读取的ObjectInputStream。 参数: InputStream in:字节输入流
特有成员方法
复制Object readObject() // 从ObjectInputStream读取对象。
举例:使用ObjectInputStream,将上例中的person.txt文件里,被序列化的Person对象数据信息,反序列化为Person对象。
反序列化的前提:
- 类必须实现Serializable接口。
- 必须存在对应类的class文件(如本例子中的Person类),如果不存在对应的class文件会抛出ClassNotFoundException异常。
使用步骤:
复制1.创建ObjectInputStream对象,构造方法中传递字节输入流。 2.使用ObjectInputStream对象中的方法readObject,读取保存对象的文件。 3.释放资源。 4.使用读取出来的对象(打印)。 import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class Demo01ObjectInputStream { public static void main(String[] args) throws IOException, ClassNotFoundException { method(); } private static void method() throws IOException, ClassNotFoundException { // 创建ObjectInputStream对象,构造方法中传递字节输入流。 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/liyihua/IdeaProjects/Study/src/view/study/demo36/person.txt")); // 使用ObjectInputStream对象中的方法readObject,读取保存对象的文件。 Object object1 = ois.readObject(); Object object2 = ois.readObject(); // 释放资源。 ois.close(); // 使用读取出来的对象(打印)。 Person person1 = (Person) object1; Person person2 = (Person) object2; System.out.println(person1); System.out.println(person2); } }
控制台输出:
复制Person{name='程序猿', age=22} Person{name='攻城狮', age=25}
transient关键字
序列化操作
一个对象要想序列化,必须满足两个条件:
- 该类必须实现java.io.Serializable接口,Serializable是一个标记接口,不实现此接口的类将不会使任可状态序列化或反序列化,会抛出NotSerializableException。
- 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient关键字修饰。
说到transient关键字,首先说一下static关键字(静态关键字)。静态优先与非静态加载到内存中,即会静态优先于对象加载到内存中,被修饰的静态成员变量是不能被序列化的,序列化的都是对象。
transient关键字:瞬态关键字,被transient关键字修饰的成员变量,不能被序列化。
举例
还是上面的两个例子:
不使用static关键字或transient关键字修饰成员变量,结果如上两个例子一样。
假如使用transient关键字修饰age成员变量,其余不更改:
复制public class Person implements Serializable { private String name; private transient 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; } }
那么第一个例子中的person.txt文件内容是这样的:
第二个例子,控制台的输出:
复制Person{name='程序猿', age=0} Person{name='攻城狮', age=0}
把transient关键字换成static关键字,效果也是一样的。
InvalidClassException异常及原理
假如序列化后,要进行反序列化之前,修改了对应Java对象的类,进行反序列化会抛出java.io.InvalidClassException异常。并且告诉我们,有序列号发生冲突。
如上例中,进行序列化后,把Person类的age修饰符从public修改为private,这个时候进行反序列化,会抛出java.io.InvalidClassException异常,并且还会告诉我们:流类序列号和本地类序列号冲突,即ObjectInputStream类序列号和Person类序列号冲突。
原理
如上面的例子:
编译器会把Person.java文件编译生成Person.class文件。又Person类又实现了Serializable接口,这时就会根据Person类的定义,给Person.class文件添加一个序列号(serialVersionUID)。每次修改类的定义,序列号都会发生改变。
进行了序列化后,再进行反序列化的时候,会使用Person.class文件的序列号和Person.txt文件的序列号对比。如果序列号一样,那么反序列化成功,否则反序列化失败,失败后,抛出java.io.InvalidClassException异常。
每次修改类的定义,都会生成新的序列号。这时候,我们可以自行给类添加一个序列号,之后,无论是否对类的定义进行修改,都不会重新生成新的序列号。
如在上例中的Person类中添加:
复制private static final long serialVersionUID = 1L;
这时候,无论是否对Person类的定义进行修改,序列号都是1L。
自行添加序列号格式:
复制[修饰符] static final long serialVersionUID = 自定义序列号值;
练习:序列化集合
当我们想要在文件中保存多个对象的时候,可以把对象存储到一个集合中,然后对集合进行序列化和反序列化。
- 将存有多个自定义对象的集合序列化操作,保存到list.txt文件中。
- 反序列化list.txt,遍历集合,打印对象信息。
创建一个Person类
复制import java.io.Serializable; public class Person implements Serializable { private String name; private 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; } }
将存有多个自定义对象的集合序列化操作,保存到list.txt文件中:
复制import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.ArrayList; public class DemoArrayPersonSerializable { public static void main(String[] args) throws IOException { method(); } private static void method() throws IOException { // 创建ObjectOutputStream对象,构造方法中传递字节输出流。 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/Users/liyihua/IdeaProjects/Study/src/view/study/demo36/list.txt")); // 创建ArrayList集合对象 ArrayList<Person> personArrayList = new ArrayList<>(); // 往集合中添加多个Person对象 personArrayList.add(new Person("程序猿", 22)); personArrayList.add(new Person("攻城狮", 25)); personArrayList.add(new Person("骇客", 19)); // 使用ObjectOutputStream对象中的方法writeObject,把ArrayList集合对象写入到文件中。 oos.writeObject(personArrayList); // 释放资源。 oos.close(); } }
生成list.txt文件,文件内容如下:
复制��srjava.util.ArrayListx����a�Isizexpwsrview.study.demo36.Person*Xs���pIageLnametLjava/lang/String;xpt 程序猿sq~t 攻城狮sq~t骇客x
反序列化list.txt,遍历集合,打印对象信息:
复制import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.util.ArrayList; public class DemoArrayObjectInputStream { public static void main(String[] args) throws IOException, ClassNotFoundException { method(); } private static void method() throws IOException, ClassNotFoundException { // 创建ObjectInputStream对象,构造方法中传递字节输入流。 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/liyihua/IdeaProjects/Study/src/view/study/demo36/list.txt")); // 使用ObjectInputStream对象中的方法readObject,读取保存对象的文件。 Object object = ois.readObject(); // 释放资源。 ois.close(); // 使用读取出来的对象(打印)。 ArrayList<Person> personArrayList = (ArrayList) object; for (Person person : personArrayList) { System.out.println(person); } } }
控制台输出:
复制Person{name='程序猿', age=22} Person{name='攻城狮', age=25} Person{name='骇客', age=19}
本文来自博客园,作者:LeeHua,转载请注明原文链接:https://www.cnblogs.com/liyihua/p/12271823.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)