爲什麼要序列化?
現在開發過程中經常遇到多個進程多個服務間需要交互,或者不同語言的服務之間需要交互,這個時候,我們一般選擇使用固定的協議,將數據傳輸過去,但是在很多語言,比如java等jvm語言中,傳輸的數據是特有的類對象,而類對象僅僅在當前jvm是有效的,傳遞給別的jvm或者傳遞給別的語言的時候,是無法直接識別類對象的,那麼,我們需要多個服務之間交互或者不同語言交互,該怎麼辦?這個時候我們就需要通過固定的協議,傳輸固定的數據格式,而這個數據傳輸的協議稱之爲序列化,而定義了傳輸數據行爲的框架組件也稱之爲序列化組件(框架)。
序列化框架中會包含兩種操作,一種將對象或數據結構轉換爲特定格式(傳輸數據協議),我們稱之爲序列化;另一種是將特定格式轉換爲對象或數據結構的過程,我們稱之爲反序列化。
序列化的目的
- 使數據可以在網絡中傳輸,或者可存儲在內存或文件中。
- 使數據可以脫離應用程序而獨立存在。
序列化使用場景
不同服務間的交互,服務可以同語言也可以不同語言。
Java語言內置序列化
序列化的實現可以分爲文本序列化、語言內置序列化和跨語言序列化,本文主要講述Java語言內置序列化。
每種語言基本都會有實現的序列化框架,此序列化的方式我們稱之爲語言內置序列化,其優點是語言提供了序列化的框架(序列化協議,序列化和反序列化操作),方便使用,缺點是交互的應用程序必須是同一種語言。
下面將講述Java需要內置序列化框架的使用,Java序列化協議是字節序列(由jvm實現)。
序列化框架使用的核心
-
序列化接口(Serializable和Externalizable)
需要序列化的類對象需要繼承此接口,只有當前接口修飾定義的類對象纔可以按照指定的方式傳輸對象。
-
序列化id-serialVersionUID
用來作爲傳輸/讀取雙端進程(javaBean)的版本是否一致的,防止我們因爲版本不一致導致的序列化失敗。
編譯器提供兩種序列化id的方式,一種是生成默認的versionID,這個值爲1L,還有一種方式是根據類名、接口名、成員方法及屬性等來生成一個 64 位的哈希字段,只要我們類名、方法名、變量有修改,或者有空格、註釋、換行等操作,計算出來的哈希字段都會不同,當然這裏需要注意,每次我們有以上的操作的時候儘量都要重新生成一次serialVerionUID(編譯器並不會給你自動修改)。
默認固定值1L和64位哈希數值不需要自己計算,繼承了序列化接口的類,如果沒有序列化id字段,在類名下會顯示黃色的波浪線,鼠標放置在類名上,選擇需要的方式即可自動添加序列化id字段(如有變動,刪除後再根據提示添加即可)。 -
對象序列化和數據寫入操作(輸出流java.io.ObjectOutputStream)
用於執行序列化操作,並將序列化後的數據寫入文件或進行傳輸。
-
數據讀取和對象反序列化操作(輸入流java.io.ObjectInputStream)
用於執行反序列化操作,將文件或從網絡接收到的數據反序列化爲對象。
案例1-基礎序列化和反序列化Serializable
下面以序列化對象存儲入文件後,讀取文件再使用對象爲例,講解序列化框架的使用,此使用同序列化數據後進行傳輸,因爲無論是數據傳輸還是存取文件,都需要用到輸入和輸出流,只是獲取輸入輸出流的參數不一致。
Person類實現
public class Person implements Serializable {
//64位哈希序列化ID
private static final long serialVersionUID = 8247451571741032209L;
private String name;
private int age;
private transient boolean isMan; // true=男,false=女
public Person(String name, int age, boolean isMan) {
this.name = name;
this.age = age;
this.isMan = isMan;
}
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;
}
public boolean isMan() {
return isMan;
}
public void setMan(boolean isMan) {
this.isMan = isMan;
}
/*此方法只是用於打印,與序列化無關*/
public String toString() {
return "Person{" + "name=" + name + ",age = " + age+",isMan = " + isMan + "}";
}
}
序列化操作
public static void writeObject() {
try {
// 0. 創建一個ObjectOutputStream輸出流
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("E:/object.txt"));
// 1. 將對象序列化到文件s
Person person = new Person("lilu", 18, true);
oos.writeObject(person);
System.out.println("Person對象已寫入object.txt文件");
} catch (Exception e) {
e.printStackTrace();
}
}
反序列化操作
public static void readObject() {
try {
// 0. 創建ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
"E:/object.txt"));
Person person1 = (Person) ois.readObject();
System.out.println("讀取到的Person對象數據如下:");
System.out.println(person1);
} catch (Exception e) {
e.printStackTrace();
}
}
main函數調用
public static void main(String[] args) {
writeObject();
readObject();
}
運行結果
Java序列化使用的總結
- Java 序列化只是針對對象的屬性(對象的類名,變量的類型、變量的數據)的傳遞,至於方法和序列化過程無關。
- 類中的某個成員變量不需要序列化,使用transient聲明該字段。
- 類靜態變量(static修飾的變量)不會被序列化。
- 成員是引用的序列化,這個引用類型對應的類也必須是可序列化的。
- 序列化具有傳遞性-當一個父類實現了序列化,那麼子類會自動實現序列化,不需要顯示實現序列化接口,反過來,子類實現序列化,而父類沒有實現序列化則序列化會失敗。 同一對象序列化多次,不會多次序列化同一對象,而是不管進行多少次序列化操作,只會得到同一個對象。
擴展
- 自定義序列化,重寫writeObject 和 readObject
- 繼承接口Externalizable
- 序列化框架的源碼詳解
- 跨語言序列化的實現