面試官:兄弟,說說你對transient的理解和感悟
素小暖:what?還有感悟?
一、基本概念
1、序列化和反序列化定義
Java序列化是指把Java對象轉換爲字節序列的過程。
Java反序列化是指把字節序列恢復爲Java對象的過程。
2、序列化和反序列化的作用
(1)序列化作用
在傳遞和保存對象時,保存對象的完整性和可傳遞性。
對象轉換爲有序字節流,可以在網絡上傳輸或者保存在本地文件(一般json/xml文件居多)中。
(2)反序列化作用
根據字節流中保存的對象狀態及描述信息,通過反序列化重建對象。
二、序列化和反序列化的優缺點
1、優點
(1)將對象轉爲字節流存儲到硬盤上,當JVM停機的時候,字節流還會在硬盤上等待,等待下一次JVM啓動,把序列化的對象,通過反序列化轉爲原來的對象,並且序列化的二進制序列能夠減少存儲空間(永久性保存對象)。
(2)序列化爲字節流形式的對象可以進行網絡傳輸(二進制形式)。
(3)通過序列化可以在進程間傳遞對象。
2、缺點
(1)無法跨語言
無法跨語言是Java序列化最致命的問題。
對於跨進程的服務調用,服務提供者可能是Java之外的其它語言,當我們需要和其它語言交互時,Java序列化就難以勝任。
事實上,目前幾乎所有流行的Java RPC通信框架,都沒有使用Java序列化作爲編解碼框架,原因就是它無法跨語言,而這些RPC框架往往需要支持跨語言調用。
Java RPC通信框架簡介
RPC是遠程過程調用的簡稱,廣泛應用在大規模分佈式應用中,作用是有助於系統的垂直拆分,使系統更易擴展。Java中RPC框架比較多,各有特色,廣泛使用的有RMI、Hession、Dubbo等。
1、RMI(遠程方法調用):
JAVA自帶的遠程方法調用工具,不過有一定的侷限性,畢竟是JAVA語言最開始時的設計,後來很多框架的原理都基於RMI。
2、Hessian(基於HTTP的遠程方法調用):
基於HTTP協議傳輸,在性能方面還不夠完美,負載均衡和失效轉移依賴於應用的負載均衡器,Hessian的使用則與RMI類似,區別在於淡化了Registry的角色,通過顯示的地址調用,利用HessianProxyFactory根據配置的地址create一個代理對象,另外還要引入Hessian的Jar包。
3、Dubbo(淘寶開源的基於TCP的RPC框架)
Dubbo是一款高性能、輕量級的開源Java RPC框架,它提供了三大核心能力:面向接口的遠程方法調用,智能容錯和負載均衡,以及服務自動註冊和發現。
(2)序列化後流的長度比通過緩衝區處理要大的多。
(3)序列化性能太低
三、序列化使用場景
1、分佈式傳遞對象,或者網絡傳輸,需要序列化
2、我調用你的jvm的方法,結果返回到我的jvm上進行處理
3、序列化可以保持對象的狀態
比如:tomcat關閉以後會把session對象序列化到SESSIONS.ser文件中,等下次啓動的時候就把這些session再加載到內存裏面來。
4、數據傳輸並復原
在j2ee中頁面與後臺使用的比較多。尤其是在列表中的時候使用尤爲突出。
比如:一個人員的列表保存起來的話,你可以將這個列表序列化,傳到後臺,然後再反序列化成person對象直接進行對象的保存。
5、比如EJB遠程調用 分佈式存儲,緩存存儲等
6、像銀行卡、密碼這些字段不能被序列化
四、序列化和反序列化的注意事項
1、Java序列化的方式
實現 Serializable 接口:可以自定義 writeObject、readObject、writeReplace、readResolve 方法,會通過反射調用。
實現 Externalizable 接口:需要實現 writeExternal 和 readExternal 方法。
2、序列化ID問題
虛擬機是否允許反序列化,不僅取決於類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。
3、靜態字段不會序列化
序列化時不保存靜態變量,這是因爲序列化保存的是對象的狀態,靜態變量屬於類的狀態,因此序列化並不保存靜態變量。
4、transient
transient代表對象的臨時數據。
如果你不想讓對象中的某個成員被序列化可以在定義它的時候加上 transient 關鍵字進行修飾,這樣,在對象被序列化時其就不會被序列化。
transient 修飾過的成員反序列化後將賦予默認值,即 0 或 null。
有些時候像銀行卡號這些字段是不希望在網絡上傳輸的,transient的作用就是把這個字段的生命週期僅存於調用者的內存中而不會寫到磁盤裏持久化。
5、父類的序列化
當一個父類實現序列化,子類自動實現序列化;而子類實現了 Serializable 接口,父類也需要實現Serializable 接口。
6、當一個對象的實例變量引用其他對象,序列化該對象時也把引用對象進行序列化
7、並非所有的對象都可以序列化
(1)安全方面的原因,比如一個對象擁有private,public等field,對於一個要傳輸的對象,比如寫到文件,或者進行RMI傳輸等等,在序列化進行傳輸的過程中,這個對象的private等域是不受保護的;
(2)資源分配方面的原因,比如socket,thread類,如果可以序列化,進行傳輸或者保存,也無法對他們進行重新的資源分配,而且,也是沒有必要這樣實現;
8、序列化解決深拷貝問題
如果一個對象的成員變量是一個對象,那麼這個對象的數據成員也會被保存,這是能用序列化解決深拷貝的重要原因。
五、代碼實例
1、實體類
package javase.transientpackage;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password;
//construct、setter、getter
}
2、ObjectOutputStream實現序列化和ObjectInputStream實現反序列化
package javase.transientpackage;
import java.io.*;
public class TransientTest {
public static void main(String[] args) {
try {
SerializeUser();
DeSerializeUser();
} catch (IOException e) {
e.printStackTrace();
}catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//序列化
private static void SerializeUser() throws IOException{
User user = new User();
user.setUsername("素小暖");
user.setPassword("123456");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://data.txt"));
oos.writeObject(user);
oos.close();
System.out.println("普通字段序列化:username= "+user.getUsername());
System.out.println("添加了transient關鍵字序列化:password= "+user.getPassword());
}
//反序列化
private static void DeSerializeUser() throws IOException, ClassNotFoundException {
File file = new File("D://data.txt");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
User user = (User)ois.readObject();
System.out.println("普通字段反序列化:username= "+user.getUsername());
System.out.println("添加了transient關鍵字反序列化:password= "+user.getPassword());
}
}