Java 序列化 之 Serializable

概念

序列化:就是把對(duì)象轉(zhuǎn)化成字節(jié)步咪。
反序列化:把字節(jié)數(shù)據(jù)轉(zhuǎn)換成對(duì)象论皆。

對(duì)象序列化場(chǎng)景:

1、對(duì)象網(wǎng)絡(luò)傳輸
例如:在微服務(wù)系統(tǒng)中或給第三方提供接口調(diào)用時(shí)猾漫,使用rpc進(jìn)行調(diào)用点晴,一般會(huì)把對(duì)象轉(zhuǎn)化成字節(jié)序列,才能在網(wǎng)絡(luò)上傳輸悯周;接收方則需要把字節(jié)序列再轉(zhuǎn)化為java對(duì)象粒督。

2、對(duì)象保存至文件中
例如:hibernate中的二級(jí)緩存:把從數(shù)據(jù)庫(kù)中查詢出的對(duì)象禽翼,序列化轉(zhuǎn)存到硬盤中屠橄,下次讀取的時(shí)候,首先從內(nèi)存中找是否有該對(duì)象闰挡,如果沒有在去二級(jí)緩存(硬盤)中去查找锐墙。減少數(shù)據(jù)庫(kù)的查詢次數(shù),提升性能长酗。

3溪北、tomcat的鈍化和活化

  • tomcat 的session 鈍化和活化之 StandarManager :
    當(dāng)Tomcat服務(wù)器關(guān)閉或者重啟時(shí)tomcat服務(wù)器會(huì)將當(dāng)前內(nèi)存中的session對(duì)象鈍化到服務(wù)器文件系統(tǒng)中;
    另一種情況是web應(yīng)用程序被重新加載時(shí)(其實(shí)原理也是重啟tomcat),內(nèi)存中的session對(duì)象也會(huì)被鈍化到服務(wù)器的文件系統(tǒng)中
    當(dāng)系統(tǒng)啟動(dòng)時(shí)之拨,會(huì)把序列化到硬盤上session重新加載到內(nèi)存中來茉继。這樣用戶還保持這登錄狀態(tài),提供系統(tǒng)的可用性蚀乔。
    這樣馒疹,tomcat重啟,如果用戶在tomcat重啟之前登錄過乙墙,然后在tomcat重啟后可以不需要登錄(前提是session沒過期前,默認(rèn)是30分鐘過期)生均。

  • tomcat 的session 鈍化和活化之 Persistentmanager:
    當(dāng)網(wǎng)站有大量用戶訪問的時(shí)候听想,服務(wù)器會(huì)創(chuàng)建大量的session,會(huì)占用大量的服務(wù)器內(nèi)存資源马胧,當(dāng)用戶開著瀏覽器一分鐘不操作頁面的話建議將session鈍化汉买,將session生成文件放在tomcat工作目錄下。

可參考 : Tomcat 之 Session的活化和鈍化 源碼分析

1. java 序列化 Serializable

java 中只要對(duì)象實(shí)現(xiàn)了 java.io.Serializable 就可以進(jìn)行序列化佩脊。

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;
    }
    @Override
    public String toString() {
        return "User [userName=" + userName + ", password=" + password + ", addr=" + addr + "]";
    }
}

該 User 類實(shí)現(xiàn)了 Serializable 接口蛙粘,那么該類應(yīng)該怎么序列化和發(fā)序列化呢?

2. ObjectInputStream 和 ObjectOutputStream

Java IO 包中為我們提供了 ObjectInputStream 和 ObjectOutputStream 兩個(gè)類威彰。
java.io.ObjectOutputStream 類實(shí)現(xiàn)類的序列化功能出牧。
java.io.ObjectInputStream 類實(shí)現(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 實(shí)例序列化到 d:\user 文件中歇盼。
  2. 使用 ObjectInputStream 把 d:\user 文件中的數(shù)據(jù)反序列化成 user2 實(shí)舔痕,并打印。

如果考慮安全問題豹缀,我們不想把密碼序列化進(jìn)行保存伯复,那么該怎么做呢?

3. transient關(guān)鍵字

當(dāng)某個(gè)字段被聲明為transient后邢笙,默認(rèn)序列化機(jī)制就會(huì)忽略該字段啸如。此處將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)村]


當(dāng)我們把 User 對(duì)象序列化保存到文件中,這時(shí) User 類結(jié)構(gòu)添加了一個(gè)新字段妇汗,那么它能成功反序列化嗎债鸡?

serialVersionUID的作用

User 類中添加一個(gè)新屬性 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í)行反序列化報(bào) java.io.InvalidClassException 異常铛纬。這是由于 User 類修改了厌均,
也就是修改過后的class,不兼容了告唆,處于安全機(jī)制考慮棺弊,程序拋出了錯(cuò)誤晶密,并且拒絕載入。從異常信息中可以看出模她,它是根據(jù) serialVersionUID 值進(jìn)行判斷類是否修改過稻艰。

如果在添加新字段 email 后,還可以繼續(xù)加載之前的字段怎么辦呢侈净?

我們可以在類中添加 serialVersionUID 屬性字段尊勿。

serialVersionUID 的值和報(bào)錯(cuò)中的 "stream classdesc serialVersionUID" 的值一樣就可以反序列化了。

如果類中沒有顯示的聲明 serialVersionUID 屬性畜侦,那么java編譯器會(huì)自動(dòng)為我們生成一個(gè) serialVersionUID (應(yīng)該是根據(jù) 屬性和方法進(jìn)行摘要算出來的元扔,方法里面內(nèi)容變動(dòng) serialVersionUID 的值不會(huì)改變。)

如果 User 對(duì)象升級(jí)版本旋膳,修改了結(jié)構(gòu)澎语,而且不想兼容之前的版本,那么只需要修改下 serialVersionUID 的值就可以了验懊。

建議擅羞,每個(gè)需要序列化的對(duì)象,都要添加一個(gè) serialVersionUID 字段义图。

如果需要把設(shè)置的 transient 的字段也需要序列化和發(fā)序列化减俏,我們應(yīng)該怎么辦?我們需要對(duì)密碼加密序列化碱工,反序列化后解密處理垄懂,又應(yīng)該怎么做?

readObject 和 writeObject

crypto 方法痛垛,實(shí)現(xiàn)加解密功能草慧。
    /**
     * 簡(jiǎn)單加密加密解密字符串 加密解密思路:先將字符串變成byte數(shù)組,再將數(shù)組每位與key做位運(yùn)算匙头,得到新的數(shù)組就是加密或解密后的byte數(shù)組.
     * 知識(shí):^ 是java位運(yùn)算漫谷,可以百度了解下,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;
    }

我們只需要在當(dāng)前 User 類中添加 readObject() 和 writeObject() 方法舔示,在 writeObject 方法中實(shí)現(xiàn)對(duì) password 的字段加密,在 readObject 方法中實(shí)現(xiàn)對(duì) password 字段解密电抚,并賦值給 User 對(duì)象即可惕稻。

readObject() 和 writeObject() 可以實(shí)現(xiàn)對(duì) transient 和 非transient字段進(jìn)行序列化。

ArrayList 序列化源碼分析

我們知道蝙叛,ArrayList 是通過數(shù)組進(jìn)行存儲(chǔ)數(shù)據(jù)的俺祠,當(dāng)數(shù)組中元素達(dá)到數(shù)組的最大容量時(shí),會(huì)自動(dòng)生成一個(gè)更大的數(shù)組,并復(fù)制到更大的數(shù)組中蜘渣。


打開ArrayList 源碼淌铐,我們可以知道,數(shù)據(jù)是存儲(chǔ)在 Object[] elementData 數(shù)組中蔫缸。
該屬性是 transient 關(guān)鍵字修飾的腿准,通過上面代碼可以知道,用 transient 關(guān)鍵字修飾的字段拾碌,默認(rèn)是不能被序列化的吐葱。ArrayList 如果要實(shí)現(xiàn)序列化,那么就必須通過 readObject() 和 writeObject() 方法去實(shí)現(xiàn)序列化校翔,那么他這是多此一舉嗎弟跑?

writeObject() 方法


通過源碼,我們可以看到展融,ArrayList 序列化數(shù)組元素時(shí)做了優(yōu)化。
因?yàn)?ArrayList 的 elementData 數(shù)組大小豫柬,不是ArrayList 的實(shí)際容量告希,這里只把實(shí)際存儲(chǔ)在 elementData中的數(shù)據(jù),進(jìn)行序列化烧给。這樣減少了序列化的流大小燕偶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市础嫡,隨后出現(xiàn)的幾起案子指么,更是在濱河造成了極大的恐慌,老刑警劉巖榴鼎,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伯诬,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡巫财,警方通過查閱死者的電腦和手機(jī)盗似,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來平项,“玉大人赫舒,你說我怎么就攤上這事∶銎埃” “怎么了接癌?”我有些...
    開封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)扣讼。 經(jīng)常有香客問我缺猛,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任枯夜,我火速辦了婚禮弯汰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘湖雹。我一直安慰自己咏闪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開白布摔吏。 她就那樣靜靜地躺著鸽嫂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪征讲。 梳的紋絲不亂的頭發(fā)上据某,一...
    開封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音诗箍,去河邊找鬼癣籽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛滤祖,可吹牛的內(nèi)容都是我干的筷狼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼匠童,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼埂材!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起汤求,我...
    開封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤俏险,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后扬绪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竖独,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年挤牛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了预鬓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赊颠,死狀恐怖格二,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情竣蹦,我是刑警寧澤顶猜,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站痘括,受9級(jí)特大地震影響长窄,放射性物質(zhì)發(fā)生泄漏滔吠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一挠日、第九天 我趴在偏房一處隱蔽的房頂上張望疮绷。 院中可真熱鬧,春花似錦嚣潜、人聲如沸冬骚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽只冻。三九已至,卻和暖如春计技,著一層夾襖步出監(jiān)牢的瞬間喜德,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工垮媒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舍悯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓睡雇,卻偏偏與公主長(zhǎng)得像萌衬,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子入桂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容