4、对象流、序列化Serializable
1.对象流的作用
对象的输入输出流主要作用是 读取和写出对象的信息。对象信息一旦写出到硬盘文件中,就可以做到持久化。
对象输入流:ObjectInputStream
对象输出流:ObjectOutputStream
2.对象流的使用步骤
1.找到目标文件
2.搭建数据通道
2.1 创建数据输出/输入流对象
2.2 创建对象输出/输入流对象,并传入数据流对象
3.把对象写出/读入
4.关闭资源
3.案例
需求:将用户的信息进行持久化保存,并读取硬盘文件中的用户信息
package character;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class Dome4 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
File file = new File("E:\\aa\\obj.txt");
List<User> inUsers = new ArrayList<User>();//用户集合
// 用户对象
User user1 = new User();
User user2 = new User();
User user3 = new User();
User user4 = new User();
user1.setUsername("aaa");
user2.setUsername("bbb");
user3.setUsername("ccc");
user4.setUsername("ddd");
user1.setPassword("111111");
user2.setPassword("222222");
user3.setPassword("333333");
user4.setPassword("444444");
// 将对象添加到集合中
inUsers.add(user1);
inUsers.add(user2);
inUsers.add(user3);
inUsers.add(user4);
//序列化 将用户信息写出到硬盘文件
testWriteInfo(file, inUsers);
//反序列化 从硬盘中读取用户信息
List<User> outUsers = testReadInfo(file);
for (User user : outUsers) {
System.out.println(user.toString());
}
}
// 序列化:将内存中的数据以二进制的形式写出到硬盘文件中的过程,叫做序列化。序列化可以实现数据持久化。
public static void testWriteInfo(File file, List<User> lists) throws IOException {
// 搭建数据通道
FileOutputStream fileOutputStream = new FileOutputStream(file, true);
//判断是否为第一次写入数据
if (file.length() < 1) {
// 创建对象的输出流对象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
for (User user : lists) {
// 将对象写出到硬盘文件
objectOutputStream.writeObject(user);
}
// 关闭资源
objectOutputStream.close();
} else {
// 创建对象的输出流对象
MyOutputStream myOutputStream = new MyOutputStream(fileOutputStream);
for (User user : lists) {
// 创建对象的输出流对象
myOutputStream.writeObject(user);
}
// 关闭资源
myOutputStream.close();
}
}
// 反序列化
public static List<User> testReadInfo(File file) throws IOException, ClassNotFoundException {
// 搭建数据通道
FileInputStream fileInputStream = new FileInputStream(file);
// 创建对象的输入流对象
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
// 传输数据
List<User> users = new ArrayList<User>();//集合,用于存储用户信息
while (fileInputStream.available() > 0) {
User user = (User) objectInputStream.readObject();
users.add(user);//将用户信息添加到list集合中
}
//关闭资源
objectInputStream.close();
return users;
}
}
// 用户类 Serializable 这个接口没有任何方法存在,他只是一个标识接口而已。实现Serializable接口才能进行序列化与反序列化操作,
class User implements Serializable {
// serialVersionUID是用来记录class文件的版本信息的,创建对象时肯定是依赖对象所属的类的 class文件的。SerializableUID这个数字是通过工程名、包名、类名、成员(属性、方法)计算而出的。一旦这些信息发生变化,这个数字就会发生变化。当程序的SerialVersionUID与硬盘文件中的class文件所记载的serialVersionUID不一致时,就不能实现反序列化操作。
private static final long serialVersionUID = 1L;// 表示序列化的版本号,标识符
private String username;// 用户名
private String password;// 密码
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "[ 用户名:" + this.username + ", 密码:" + this.password + " ]";
}
}
// 重写对象输出流的writeStreamHeader方法
class MyOutputStream extends ObjectOutputStream {
public MyOutputStream(OutputStream out) throws IOException {
super(out);
}
// 如果想要一次性反序列化硬盘上的所有对象的信息,那么就需要重写对象输出流的writeStreamHeader()方法,并在写入硬盘是判断是否是第一次写入数据。
@Override
protected void writeStreamHeader() throws IOException {
return;
}
}
4.对象流、序列化注意事项
1. Serializable:序列化标识接口
1.1 如果对象需要被写出到硬盘上,那么对象所属的类必须实现Serializable接口,Serializable接口中没有任何的方法,它只是一个标识接口。
1.2 如果一个对象的所属类维护了另一个类的引用,那么另一个类也需要实现Serializable接口。
2. 序列化:就是将java对象转换成字节序列的过程(将数据从内存存储到硬盘的过程)
2.1 transient:短暂的,不要序列化的
(1)如果对象的某个属性不详被序列化到硬盘上,那么可以用transient修饰。
(2)执行序列化时,JVM会自动忽略transient修饰的变量的初始值,而是将默认值保存到硬盘中。
(3)transient修饰符只适用于变量,不适用于方法和类。
2.2 transient不能与哪些修饰符同用?
(1)static 与transient:静态变量是属于类的,不是属于对象的,所以被static修饰的属性不参加序列化。
(2)final 与transient:final修饰的变量值是固定的,变量将直接通过值参与序列化,因此将final修饰的变量声明为transient,是没用的。
3. 反序列化:就是将字节序列转成成对象的过程(将数据从硬盘存储到内存的过程)
3.1 对象的反序列化在创建对象的时候并不会调用对象的构造方法。
4. serialVersionUID :序列化的版本号,标识符
4.1 serialVersionUID是用来记录class文件的版本信息的。当我们创建对象时,是需要依赖对象所属类的class文件的。serialVersionUID的值是通过工程名、包名、类名、成员计算而来的,一旦这些信息发生变化,这个数字也会改变。
4.2 当我们使用ObjectOutputStream进行反序列化操作时,JVM会先读取硬盘文件中的SerialVersionUID的值,然后与程序中对象所属类的SerialVersionUID进行比较,如果这两个值不相等,那么反序列化就失败了。
4.3 如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始实现Serializable接口时,就指定该类的SerialVersionUID的值,这样在序列化与反序列化的时候,JVM就不会自己去计算这个类的SerialVersionUID的值了。