序列化
Java的序列化机制和反序列化机制如下图
对象的序列化与反序列化.png
一、ObjectOutputStream 类
java.io.ObjectOutputStream
;【继承OutputStream】类将java对象的原始数据类型写出到文件,实现对象的持久存储。
1.构造方法
-
public ObjectOutputStream(OutputStream out)
: 创建一个指定OutPutStream的ObjectOutputStream
注意:Bufferedxxx没有继承四个流的抽象类!
FileOutputStream fileOut = new FileOutputStream("F:\\test\\fileOut.txt");
ObjectOutputStream objFileOut = new ObjectOutputStream(fileOut);
2.序列化操作
- 一个对象想要实现序列化,需满足两个条件
- 该类实现
java.io.Serializable
接口;是一个标记接口,不实现此接口的类将不会使任何状态序列化或者反序列化,会抛出NotSerializableException
-
transient
关键字:该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化,则该属性必须注明是瞬态的,使用transient关键字修饰。
- 该类实现
- 写出对象的方法(省略public,ObjectOutputStream的方法):
-
final void writeObject(Object obj)
: 将指定对象写出
-
- 使用步骤:
- 创建ObjectOutputStream对象,构造方法中传递字节输出流
- 使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
- 释放资源
import java.io.*;
class Serial implements java.io.Serializable{
public String name;
public String address;
public int tall; // 身高
public transient int age; //transient修饰瞬态成员,不会被序列化
public Serial(String name, String address, int tall, int age) { this.name = name;this.address = address;this.tall = tall;this.age = age; }
@Override
public String toString() { return "Serial{" + "name='" + name + '\'' + ", address='" + address + '\'' + ", tall=" + tall + ", age=" + age + '}'; }
}
public class Main {
public static void main(String[] args) throws IOException {
Serial ser = new Serial("小明", "安徽省宿州市灵璧县向阳乡",170,22);
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("F:\\test\\serial.txt"));
try(out) {
// 序列化并写入
out.writeObject(ser);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
// --------------------------------------------- //
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("F:\\test\\serial.txt"));
Serial newSer = null;
try(in) {
// 读取并反序列化
newSer = (Serial) in.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(newSer);
// Serial{name='小明', address='安徽省宿州市灵璧县向阳乡', tall=170, age=0}
// 被transient修饰的不会被序列化
}
}
一、ObjectInputStream 类
反序列化流,将使用ObjectOutputStream序列化的原始数据恢复为对象。【继承InputStream】
1.构造方法
-
public ObjectInputStream(InputStream in)
:创建一个指定InputStream的ObjectInputStream
2.反序列化操作1
如果我们找到一个对象的class文件,可以进行反序列化操作,调用ObjectInputStream读取对象的方法
-
public final Object readObject()
:读取一个对象 - 使用步骤:
- 创建ObjectInputStream对象,构造方法中传递字节输入流
- 使用ObjectInputStream对象中的方法readObject读取保存对象的文件
- 释放资源
- 使用读取出来的对象(打印)
Serial newSer = null;
try {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("F:\\test\\serial.txt"));
// 读取并反序列化
newSer = (Serial) in.readObject();
// 释放资源
in.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
System.out.println(newSer);
// Serial{name='小明', address='安徽省宿州市灵璧县向阳乡', tall=170, age=0}
// 被transient修饰的不会被序列化
对于JVM可以反序列化对象,它必须是能够找到class文件(不一定以.class文件结尾)的类。如果找不到该类的class文件,则抛出一个ClassNotFoundException
异常
2.反序列化操作2
当JVM反序列换对象是,能够找到class文件(不一定以.class文件结尾),但是class文件在序列化对象之后发生了修改(比如打开文件乱写写东西),那么反序列化也会失败,会抛出一个InvalidClassException
异常,原因可能如下
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参构造方法
反序列化的前提:
- 类必须实现Serializable
- 必须存在类对应的class文件
Serializable
接口给需要序列化的类,提供了一个序列版本号:serialVersionUID
(不能随便改名字!),系统默认是根据类的相关属性进行设计的,该版本号的目的在于验证序列化对象和对应的类是否版本匹配。
【前提是统一serialVersionUID
,需要手动指定】在类中添加新的属性(之前序列化的可能没有),重新编译,可以反序列化,该属性赋值为默认值
不会被序列化的变量:
- static关键字:静态关键字,静态优先于非静态加载到内存中(静态优先于对象进入到内存中),被static修饰的成员变量不被序列化的,序列化的都是对象
- transient关键字:瞬态关键字,被transient修饰成员变量,不会被序列化
class Serial implements java.io.Serializable{
// 加入序列版本号,
private static final long serialVersionUID = 1L;
public String name;
public String address;
public int tall; // 身高
public transient int age; //transient修饰瞬态成员,不会被序列化
// 【前提是serialVersionUID一样,需要手写】添加新的属性(之前序列化的可能没有),重新编译,可以反序列化,该属性赋值为默认值
public int newAdd;
public Serial(String name, String address, int tall, int age) { this.name = name;this.address = address;this.tall = tall;this.age = age; }
@Override
public String toString() { return "Serial{" + "name='" + name + '\'' + ", address='" + address + '\'' + ", tall=" + tall + ", age=" + age + '}'; }
}
public class Serial代码与【2.反序列化操作1】代码块一样!
案例:序列化一个ArrayList
import java.io.*;
import java.util.ArrayList;
class Student implements java.io.Serializable{
private static final long serialVersionUID = 3L;
String name;
int age;
public Student(String name, int age) { this.name = name;this.age = age; }
@Override
public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; }
}
public class SerialCollection {
public static void main(String[] args) {
ArrayList<Student> arr = new ArrayList<>();
arr.add(new Student("小明", 22));
arr.add(new Student("小黄", 23));
arr.add(new Student("小黑", 33));
arr.add(new Student("小兰", 14));
arr.add(new Student("小红", 16));
arr.add(new Student("小锤", 34));
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("F:\\test\\studentarray.txt"));
out.writeObject(arr);
out.close();// 释放资源
System.out.println("写入成功,请等待两秒....");
Thread.sleep(2000);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
ArrayList<Student> receive = null;
try {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("F:\\test\\studentarray.txt")
);
receive = (ArrayList<Student>) in.readObject();
in.close(); // 释放资源
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
for (Student student : receive) {
System.out.println(student);
}
}
}