Java 序列化與反序列化
上一小節我們學習了 Java 的輸入輸出流,有了這些前置知識點,我們就可以學習 Java 的序列化了。本小節將介紹什么是序列化、什么是反序列化、序列化有什么作用,如何實現序列化與反序列化,Serializable 接口介紹,常用序列化工具介紹等內容。了解序列化的用途、學會如何進行序列化和反序列化操作是本小節的重點內容。
1. 序列化與反序列化
序列化在計算機科學的數據處理中,是指將數據結構或對象狀態轉換成可取用格式,以留待后續在相同或另一臺計算機環境中,能恢復原先狀態的過程。依照序列化格式重新獲取字節的結果時,可以利用它來產生與原始對象相同語義的副本。
很多編程語言自身就支持序列化操作。Java 語言提供自動序列化,序列化(serialize
)就是將對象轉換為字節流;與之相應對的,反序列化(deserialize
)就是將字節流轉換為對象。
需要注意的是,Java 序列化對象時,會把對象的狀態保存成字節序列,對象的狀態指的就是其成員變量,因此序列化的對象不會保存類的靜態變量。
在 Java 中,可通過對象輸出/輸入流來實現序列化/反序列化操作。 java.io
包中,提供了ObjectInputStream
類和ObjectOutputStream
用來序列化對象,這兩個類我們將在下面介紹。下面我們來介紹一下序列化的作用。
2. 序列化的作用
- 序列化可以將對象的字節序列存持久化:可以將其保存在內存、文件、數據庫中(見下圖);
- 可以在網絡上傳輸對象字節序列;
- 可用于遠端程序方法調用。

3. 實現序列化
ObjectOutputStream
類下的void writeObject(Object obj)
方法用于將一個對象寫入對象輸出流,也就是序列化;ObjectInputStream
類下的Object readObject()
方法用于讀取一個對象到輸入流,也就是反序列化。
實例代碼如下:
import java.io.*;
public class SerializeDemo1 {
static class Cat implements Serializable {
private static final long serialVersionUID = 1L;
private String nickname;
private Integer age;
public Cat() {}
public Cat(String nickname, Integer age) {
this.nickname = nickname;
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"nickname='" + nickname + '\'' +
", age=" + age +
'}';
}
}
/**
* 序列化方法
* @param filepath 文件路徑
* @param cat 要序列化的對象
* @throws IOException
*/
private static void serialize(String filepath, Cat cat) throws IOException {
// 實例化file對象
File file = new File(filepath);
// 實例化文件輸出流
FileOutputStream fileOutputStream = new FileOutputStream(file);
// 實例化對象輸出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
// 保存cat對象
objectOutputStream.writeObject(cat);
// 關閉流
fileOutputStream.close();
objectOutputStream.close();
}
/**
* 反序列化方法
* @param filepath 文件路徑
* @throws IOException
* @throws ClassNotFoundException
*/
private static void deserialize(String filepath) throws IOException, ClassNotFoundException {
// 實例化file對象
File file = new File(filepath);
// 實例化文件輸入流
FileInputStream fileInputStream = new FileInputStream(file);
// 實例化對象輸入流
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Object o = objectInputStream.readObject();
System.out.println(o);
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
String filename = "C:\\Users\\Colorful\\Desktop\\imooc\\Hello.txt";
Cat cat = new Cat("豬皮", 1);
serialize(filename, cat);
deserialize(filename);
}
}
運行結果:
Cat{nickname='豬皮', age=1}
上述代碼中,我們定義了一個Cat
類,它實現了Serializable
接口,類內部有一個private static final long serialVersionUID = 1L;
,關于這兩點,我們下面緊接著就會介紹。
除了Cat
類的定義,我們還分別封裝了序列化與反序列化的方法,并在主方法中調用了這兩個方法,實現了cat
對象的序列化和反序列化操作。
在調用序列化方法后,你會發現磁盤中的Hello.txt
文件中被cat
對象寫入了序列化后的數據:

4. Seralizable 接口
被序列化的類必須是Enum
、Array
或Serializable
中的任意一種類型。
如果要序列化的類不是枚舉類型和數組類型的話,則必須實現java.io.Seralizable
接口,否則直接序列化將拋出NotSerializableException
異常。
4.1 serialVersionUID
serialVersionUID
是 Java 為每個序列化類產生的版本標識。它可以用來保證在反序列化時,發送方發送的和接受方接收的是可兼容的對象。如果接收方接收的類的 serialVersionUID
與發送方發送的 serialVersionUID
不一致,會拋出 InvalidClassException
。
4.2 默認序列化機制
如果僅僅只是讓某個類實現 Serializable
接口,而沒有其它任何處理的話,那么就會使用默認序列化機制。
使用默認機制,在序列化對象時,不僅會序列化當前對象本身,還會對其父類的字段以及該對象引用的其它對象也進行序列化。同樣地,這些其它對象引用的另外對象也將被序列化,以此類推。所以,如果一個對象包含的成員變量是容器類對象,而這些容器所含有的元素也是容器類對象,那么這個序列化的過程就會較復雜,開銷也較大。
4.3 transient 關鍵字
在現實應用中,有些時候不能使用默認序列化機制。比如,希望在序列化過程中忽略掉敏感數據,或者簡化序列化過程。下面將介紹若干影響序列化的方法。
當某個字段被聲明為 transient
后,默認序列化機制就會忽略該字段。
可以嘗試將實例代碼中Cat
類的成員變量age
聲明為transient
:
// 僅部分代碼
static class Cat implements Serializable {
transient private Integer age;
}
運行程序,我們會發現成員變量age
沒有被序列化。
5. 常用序列化工具
Java 官方的序列化存在很多缺點,因此,開發者們更傾向于使用優秀的第三方序列化工具來替代 Java 自身的序列化機制。
Java 官方的序列化主要體現在以下方面:
- 性能問題:序列化后的數據相對于一些優秀的序列化的工具,還是要大不少,這大大影響存儲和傳輸的效率;
- 繁瑣的步驟:Java 官方的序列化一定需要實現
Serializable
接口,略顯繁瑣,而且需要關注serialVersionUID
; - 無法跨語言使用:序列化的很大一個目的就是用于不同語言來讀寫數據。
下面列舉了一些優秀的序列化工具:
6. 小結
通過本小節的學習,我們知道了序列化(serialize
)就是將對象轉換為字節流,反序列化(deserialize
)就是將字節流轉換為對象。想要實現序列化,就必須繼承Seralizable
接口,serialVersionUID
是 Java 為每個序列化類產生的版本標識。當某個字段被聲明為 transient
后,默認序列化機制就會忽略該字段。學會根據自己的應用場景選擇使用序列化工具。