## 序列化概念
序列化:指堆內存中的java對象數據,通過某種方式把對存儲到磁盤文件中,或者傳遞給其他網絡節點(網絡傳輸)。這個過程稱為序列化,通常是指將數據結構或對象轉化成二進制的過程。即將對象轉化為二進制,用于保存,或者網絡傳輸。
反序列化:把磁盤文件中的對象數據或者把網絡節點上的對象數據,恢復成Java對象模型的過程。也就是將在序列化過程中所生成的二進制串轉換成數據結構或者對象的過程與序列化相反,將二進制轉化成對象。
## 序列化作用
- 想把內存中的對象保存到一個文件中或者數據庫中時候;
- 想用套接字在網絡上傳送對象的時候;
- 想通過RMI傳輸對象的時候;
在很多應用中,需要對某些對象進行序列化,讓它們離開內存空間,入住物理硬盤,以便長期保存。比如最常見的是Web服務器中的Session對象,當有 10萬用戶并發訪問,就有可能出現10萬個Session對象,內存可能吃不消,于是Web容器就會把一些seesion先序列化到硬盤中,等要用了,再把保存在硬盤中的對象還原到內存中。
當兩個進程在進行遠程通信時,彼此可以發送各種類型的數據。無論是何種類型的數據,都會以二進制序列的形式在網絡上傳送。發送方需要把這個Java對象轉換為字節序列,才能在網絡上傳送;接收方則需要把字節序列再恢復為Java對象。
## 序列化API
JDK類庫中的序列化API:
java.io.ObjectOutputStream代表對象輸出流,它的writeObject(Object obj)方法可對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中。
java.io.ObjectInputStream代表對象輸入流,它的readObject()方法從一個源輸入流中讀取字節序列,再把它們反序列化為一個對象,并將其返回。
只有實現了Serializable和Externalizable接口的類的對象才能被序列化。Externalizable接口繼承自 Serializable接口,實現Externalizable接口的類完全由自身來控制序列化的行為,而僅實現Serializable接口的類可以 采用默認的序列化方式 。
## 序列化步驟
對象序列化包括如下步驟:
- 創建一個對象輸出流,它可以包裝一個其他類型的目標輸出流,如文件輸出流;
- 通過對象輸出流的writeObject()方法寫對象。
對象反序列化的步驟如下:
- 創建一個對象輸入流,它可以包裝一個其他類型的源輸入流,如文件輸入流;
- 通過對象輸入流的readObject()方法讀取對象。
## 序列化實現
要實現對象的序列化,最直接的操作就是實現Serializable接口,使用IO流中的對象流可以實現序列化操作,將對象保存到文件,再讀取出來。
首先創建一個對象,并實現Serializable接口:
```java
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
```
用對象流寫一個保存對象與讀取對象的工具類:
```java
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializeUtil {
// 保存對象,序列化
public static void saveObject(Object object) throws Exception {
ObjectOutputStream out = null;
FileOutputStream fout = null;
try {
fout = new FileOutputStream("D:/user.txt");
out = new ObjectOutputStream(fout);
out.writeObject(object);
} finally {
fout.close();
out.close();
}
}
// 讀取對象,反序列化
public static Object readObject() throws Exception {
ObjectInputStream in = null;
FileInputStream fin = null;
try {
fin = new FileInputStream("D:/user.txt");
in = new ObjectInputStream(fin);
Object object = in.readObject();
return object;
} finally {
fin.close();
in.close();
}
}
}
```
測試:
```java
public class Main {
public static void main(String[] args) {
User user = new User();
user.setName("海綿寶寶");
user.setAge(20);
// 保存
try {
SerializeUtil.saveObject(user);
} catch (Exception e) {
System.out.println("保存時異常:" + e.getMessage());
}
// 讀取
User userObject;
try {
userObject = (User) SerializeUtil.readObject();
System.out.println(userObject);
} catch (Exception e) {
System.out.println("讀取時異常:" + e.getMessage());
}
}
}
/*
* 運行結果:User [name=海綿寶寶, age=20]
*/
```
## serialVersionUID的作用
serialVersionUID:字面意思上是序列化版本號,凡是實現Serializable接口的類都有一個表示序列化版本標識符的靜態變量。
```java
private static final long serialVersionUID = 1L;
```
實現Serializable接口的類如果類中沒有添加serialVersionUID,那么就會出現如下的警告提示:

根據代碼提示可以看出serialVersionUID有兩種生成方式:
采用第一種方式生成的serialVersionUID是1L,例如:
```java
private static final long serialVersionUID = 1L;
```
采用第二種方式生成的serialVersionUID是根據類名,接口名,方法和屬性等來生成的,例如:
```java
private static final long serialVersionUID = 3959055215634785113L;
```
當我們一個實體類中沒有顯式的定義一個名為“serialVersionUID”、類型為long的變量時,Java序列化機制會根據編譯時的class自動生成一個serialVersionUID作為序列化版本比較,這種情況下,只有同一次編譯生成的class才會生成相同的serialVersionUID。譬如,當我們編寫一個類時,隨著時間的推移,我們因為需求改動,需要在本地類中添加其他的字段,這個時候再反序列化時便會出現serialVersionUID不一致,導致反序列化失敗。那么如何解決呢?便是在本地類中添加一個“serialVersionUID”變量,值保持不變,便可以進行序列化和反序列化。
如果沒有顯示指定serialVersionUID,會自動生成一個。
只有同一次編譯生成的class才會生成相同的serialVersionUID。
但是如果出現需求變動,Bean類發生改變,則會導致反序列化失敗。為了不出現這類的問題,所以我們最好還是顯式的指定一個serialVersionUID。
## 序列化的其他問題
- 靜態變量不會被序列化( static,transient)
- 當一個父類實現序列化,子類自動實現序列化,不需要顯式實現Serializable接口。
- 當一個對象的實例變量引用其他對象,序列化該對象時也把引用對象進行序列化。
- 子類序列化時有一下兩種:
如果父類沒有實現Serializable接口,沒有提供默認構造函數,那么子類的序列化會出錯;
如果父類沒有實現Serializable接口,提供了默認的構造函數,那么子類可以序列化,父類的成員變量不會被序列化。如果父類實現了Serializable接口,則父類和子類都可以序列化。