对象流、序列化Serializable

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的值了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章