對象流、序列化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的值了。

 

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