Java 序列化 之 Serializable
概念
序列化:就是把對象轉(zhuǎn)化成字節(jié)。
反序列化:把字節(jié)數(shù)據(jù)轉(zhuǎn)換成對象。
對象序列化場景:
1、對象網(wǎng)絡(luò)傳輸
例如:在微服務(wù)系統(tǒng)中或給第三方提供接口調(diào)用時,使用rpc進行調(diào)用,一般會把對象轉(zhuǎn)化成字節(jié)序列,才能在網(wǎng)絡(luò)上傳輸;接收方則需要把字節(jié)序列再轉(zhuǎn)化為java對象。
2、對象保存至文件中
例如:hibernate中的二級緩存:把從數(shù)據(jù)庫中查詢出的對象,序列化轉(zhuǎn)存到硬盤中,下次讀取的時候,首先從內(nèi)存中找是否有該對象,如果沒有在去二級緩存(硬盤)中去查找。減少數(shù)據(jù)庫的查詢次數(shù),提升性能。
3、tomcat的鈍化和活化
tomcat 的session 鈍化和活化之 StandarManager :
當Tomcat服務(wù)器關(guān)閉或者重啟時tomcat服務(wù)器會將當前內(nèi)存中的session對象鈍化到服務(wù)器文件系統(tǒng)中;
另一種情況是web應(yīng)用程序被重新加載時(其實原理也是重啟tomcat),內(nèi)存中的session對象也會被鈍化到服務(wù)器的文件系統(tǒng)中
當系統(tǒng)啟動時,會把序列化到硬盤上session重新加載到內(nèi)存中來。這樣用戶還保持這登錄狀態(tài),提供系統(tǒng)的可用性。
這樣,tomcat重啟,如果用戶在tomcat重啟之前登錄過,然后在tomcat重啟后可以不需要登錄(前提是session沒過期前,默認是30分鐘過期)。- tomcat 的session 鈍化和活化之 Persistentmanager:
當網(wǎng)站有大量用戶訪問的時候,服務(wù)器會創(chuàng)建大量的session,會占用大量的服務(wù)器內(nèi)存資源,當用戶開著瀏覽器一分鐘不操作頁面的話建議將session鈍化,將session生成文件放在tomcat工作目錄下。
可參考 : Tomcat 之 Session的活化和鈍化 源碼分析
- tomcat 的session 鈍化和活化之 Persistentmanager:
1. java 序列化 Serializable
java 中只要對象實現(xiàn)了 java.io.Serializable 就可以進行序列化。
public class User implements Serializable {private String userName;private String password;private String addr;public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getAddr() {return addr;}public void setAddr(String addr) {this.addr = addr;}@Overridepublic String toString() {return "User [userName=" + userName + ", password=" + password + ", addr=" + addr + "]";} }該 User 類實現(xiàn)了 Serializable 接口,那么該類應(yīng)該怎么序列化和發(fā)序列化呢?
2. ObjectInputStream 和 ObjectOutputStream
Java IO 包中為我們提供了 ObjectInputStream 和 ObjectOutputStream 兩個類。
java.io.ObjectOutputStream 類實現(xiàn)類的序列化功能。
java.io.ObjectInputStream 類實現(xiàn)了反序列化功能。
示例如下:
public class Test{public static void main(String[] args) throws Exception {File file = new File("d:\\a.user");ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));User user1 = new User();user1.setUserName("zhangsan");user1.setPassword("123456");user1.setAddr("北京中關(guān)村");oos.writeObject(user1);ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));User user2 = (User)ois.readObject();System.out.println(user2);} }輸出結(jié)果:
User [userName=zhangsan, password=123456, addr=北京中關(guān)村]
1. 使用 ObjectOutputStream 把 user1 實例序列化到 d:\user 文件中。
2. 使用 ObjectInputStream 把 d:\user 文件中的數(shù)據(jù)反序列化成 user2 實,并打印。
如果考慮安全問題,我們不想把密碼序列化進行保存,那么該怎么做呢?
3. transient關(guān)鍵字
當某個字段被聲明為transient后,默認序列化機制就會忽略該字段。此處將User類中的password字段聲明為transient,如下所示,
public class User implements Serializable {private String userName;private transient String password;private String addr;... ...然后在執(zhí)行Test類的 main 方法,執(zhí)行結(jié)果如下:
輸出結(jié)果:
User [userName=zhangsan, password=null, addr=北京中關(guān)村]
當我們把 User 對象序列化保存到文件中,這時 User 類結(jié)構(gòu)添加了一個新字段,那么它能成功反序列化嗎?
serialVersionUID的作用
User 類中添加一個新屬性 email 字段,如下圖:
然后再執(zhí)行反序列化
執(zhí)行結(jié)果如下:
Exception in thread “main” java.io.InvalidClassException: cn.com.infcn.serial.User; local class incompatible: stream classdesc serialVersionUID = 1318824539146791009, local class serialVersionUID = 7884536922902331245
執(zhí)行反序列化報 java.io.InvalidClassException 異常。這是由于 User 類修改了,
也就是修改過后的class,不兼容了,處于安全機制考慮,程序拋出了錯誤,并且拒絕載入。從異常信息中可以看出,它是根據(jù) serialVersionUID 值進行判斷類是否修改過。
如果在添加新字段 email 后,還可以繼續(xù)加載之前的字段怎么辦呢?
我們可以在類中添加 serialVersionUID 屬性字段。
serialVersionUID 的值和報錯中的 “stream classdesc serialVersionUID” 的值一樣就可以反序列化了。
如果類中沒有顯示的聲明 serialVersionUID 屬性,那么java編譯器會自動為我們生成一個 serialVersionUID (應(yīng)該是根據(jù) 屬性和方法進行摘要算出來的,方法里面內(nèi)容變動 serialVersionUID 的值不會改變。)
如果 User 對象升級版本,修改了結(jié)構(gòu),而且不想兼容之前的版本,那么只需要修改下 serialVersionUID 的值就可以了。
建議,每個需要序列化的對象,都要添加一個 serialVersionUID 字段。
如果需要把設(shè)置的 transient 的字段也需要序列化和發(fā)序列化,我們應(yīng)該怎么辦?我們需要對密碼加密序列化,反序列化后解密處理,又應(yīng)該怎么做?
readObject 和 writeObject
crypto 方法,實現(xiàn)加解密功能。
/*** 簡單加密加密解密字符串 加密解密思路:先將字符串變成byte數(shù)組,再將數(shù)組每位與key做位運算,得到新的數(shù)組就是加密或解密后的byte數(shù)組.* 知識:^ 是java位運算,可以百度了解下,a = b ^ skey 反之也成立,即b = a ^ skey* * @param str 解密/加密 字符串* @return* @throws Exception*/static String crypto(String str) {try {byte skey = (byte) 88; //密鑰byte[] bytes = str.getBytes("GBK");for (int i = 0; i < bytes.length; i++) {bytes[i] = (byte) (bytes[i] ^ skey);}return new String(bytes, "GBK");} catch (Exception ex) {ex.printStackTrace();}return null;}我們只需要在當前 User 類中添加 readObject() 和 writeObject() 方法,在 writeObject 方法中實現(xiàn)對 password 的字段加密,在 readObject 方法中實現(xiàn)對 password 字段解密,并賦值給 User 對象即可。
readObject() 和 writeObject() 可以實現(xiàn)對 transient 和 非transient字段進行序列化。
ArrayList 序列化源碼分析
我們知道,ArrayList 是通過數(shù)組進行存儲數(shù)據(jù)的,當數(shù)組中元素達到數(shù)組的最大容量時,會自動生成一個更大的數(shù)組,并復(fù)制到更大的數(shù)組中。
打開ArrayList 源碼,我們可以知道,數(shù)據(jù)是存儲在 Object[] elementData 數(shù)組中。
該屬性是 transient 關(guān)鍵字修飾的,通過上面代碼可以知道,用 transient 關(guān)鍵字修飾的字段,默認是不能被序列化的。ArrayList 如果要實現(xiàn)序列化,那么就必須通過 readObject() 和 writeObject() 方法去實現(xiàn)序列化,那么他這是多此一舉嗎?
writeObject() 方法
通過源碼,我們可以看到,ArrayList 序列化數(shù)組元素時做了優(yōu)化。
因為 ArrayList 的 elementData 數(shù)組大小,不是ArrayList 的實際容量,這里只把實際存儲在 elementData中的數(shù)據(jù),進行序列化。這樣減少了序列化的流大小。
想了解更多精彩內(nèi)容請關(guān)注我的公眾號
本人簡書blog地址:http://www.jianshu.com/u/1f0067e24ff8????
點擊這里快速進入簡書
GIT地址:http://git.oschina.net/brucekankan/
點擊這里快速進入GIT
總結(jié)
以上是生活随笔為你收集整理的Java 序列化 之 Serializable的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 对象占用内存大小
- 下一篇: Java 序列化之 Externaliz