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