(二)遠端服務:Java 物件序列化和反序列化
在遠端方法呼叫 RMI 學習的過程中,涉及到一個概念,序列化,本文進行詳述。
Java 物件的序列化和反序列化 的兩種應用場景
有時候需要將 Java 物件儲存永久儲存,比如儲存到檔案中,過程:
Java 物件 -> IO 物件流 -> 寫入檔案 -> 字串
。當我們需要將文件中的字串恢復為 Java 物件的時候,需要相反的過程:字串 -> 讀檔案 -> IO 流 -> (強制)轉化為 Java 物件
。
還有一種場景,在網路通訊中,物件要先變為IO流經過網路傳輸之後,再被還原為Java物件。過程:物件 -> 寫IO -> 讀IO -> 物件
序列化是一個處理物件資訊的過程
序列化 (Serialization)將物件的狀態資訊轉換為可以儲存或傳輸的形式的過程。
反序列化是將傳輸資訊恢復為物件的過程。Java 物件被序列化的兩種方式
方式一:Java 物件所屬的類需要直接或者間接實現
Serializable
介面。
方式二:Java 物件所屬的類需要直接或者間接實現Externalizable
介面。這裡需要注意,如果通過繼承實現Externalizable
介面,必須覆蓋重寫 writeExternal 和 readExternal 方法,否則子類新增屬性無法參與序列化。Serializable 和 Externalizable 的區別
Serializable 可以是間接或者直接實現,所在類的屬性都會被序列化。
Serializable 的思路是,如果沒有特殊,本類以及子類的屬性將參與序列化。
Externalizable 也可以是間接實現或者直接實現,但是其參與序列化和反序列化的屬相都需要通過重寫方法去指定。transient
實現 Serializable 的類 ,其屬性預設都會參與序列化和反序列化,如果不想某個屬性參與序列化,增加 transient 修飾即可。但是 實現 Externalizable 的類不會受transient 的影響。private transient String other;//transient 表示該欄位不參與序列化
Externalizable 需要覆蓋重寫的 writeExternal 和 readExternal 方法舉例
//宣告屬性
private String userName;
private String password;
private int age;
private int age2;
//get set方法略
//這兩個方法無需顯式呼叫,但是哪些屬性需要序列化,則需要單獨指明
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(password);
out.writeUTF(userName);
out.writeInt(age);
out.writeInt(age2);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
userName=in.readUTF();
password=in.readUTF();
age=in.readInt();
age2=in.readInt();
}
序列號的生成
Java 的序列化和反序列化工作是由 JVM 完成的,在序列化和反序列化的過程中,JVM 會根據類的路徑、名字、內容生成一個 long 型別的數字串(
當然你也可以自己指定
),這個數字串被稱為序列號。private static final long serialVersionUID = -5635658486326609381L;
序列號的生成和 JVM 的版本有關係嗎?
根據我的實驗,對於同一個路徑下,同一個類,在內容不變的情況下,JVM 生成的序列號是一致的。測試 jdk 版本有 jdk1.6、jdk1.7、jdk1.8。
序列號是允許不顯式表述的
序列號是可以不寫出來的,但是要求序列化參照的類和反序列化參照的類都不寫才可以。
序列號的意義
由於序列號的生成是和 類的路徑、名字、內容 有關,這意味著如果你的類的內容有修改,JVM 自動生成的序列號就會有變動,
如果你在類中指定了序列號,JVM 就會以你指定的為準
。這樣做是有好處的,可以提醒你遠端互動中兩端的類的版本不一致。但是這也有弊端,對於遠端呼叫來說,如果一方
實體類有變動,那麼雙方生成的序列號可能就不一樣。所以序列號是為了保證序列化和反序列化雙方的一致性。一定要顯式的指定序列號
由於我們無法確保類的內容不會更改,所以一定要寫序列號,這個是可以自定義的,比如寫成
1L
。Java 物件序列化會影響效能且需要平臺一致性
顯然序列化會影響效能,而且需要同為 Java 平臺。
序列化應用的簡單測試——以物件儲存到 txt 中為例
- 實體類
package common;
import java.io.Serializable;
public class Model implements Serializable{
private static final long serialVersionUID = -3265803653258922833L;
private String userName;
private String password;
private transient int age;
private int age2;
private transient String other;//transient 表示該欄位不參與序列化
//get\set 方法 略
//如果 implements Externalizable ,需在下面的方法中指明需序列化個反序列化欄位
//序列化欄位
/*@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(password);
out.writeUTF(userName);
out.writeInt(age);
out.writeInt(age2);
}
//反序列化欄位
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
userName=in.readUTF();
password=in.readUTF();
age=in.readInt();
age2=in.readInt();
}*/
}
- 測試方法
package common;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
//檔案路徑和名字
private static String filePath="D://a.txt";
public static void main(String[] args) {
Model m=new Model();
m.setUserName("jecket");
m.setPassword("123456");
m.setOther("other");
//寫到檔案
write(m);
//從檔案讀取
read();
}
//將物件儲存到 檔案
public static void write(Model m){
try {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeObject(m);
oos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//從物件中讀取 檔案
public static void read(){
try {
@SuppressWarnings("resource")
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filePath));
Model m=(Model)ois.readObject();
System.out.println(m.getUserName());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}