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