色談Java序列化:女孩子慎入 - 第280篇

優美格式地址:https://mp.weixin.qq.com/s/8tcHFjY1VawAkzdArkSzJQ 

悟纖:師傅,最近我老是碰到一個異常:java.io.NotSerializableException

師傅:徒兒你這是沒有序列化。

悟纖:序列化這是啥?爲啥要序列化呢?

師傅:好了,咱們今天就來講一講。

爲了提高訪問速度,我們會使用到緩存,比如memcached來緩存一些不頻繁變化的數據。這時候,將對象存到緩存管理器之後,那麼可能就會遇到如上徒兒說的異常了。

BTW:別人是轉角遇到愛,愛徒是轉角遇到異常。

對於NotSerializableException在Java中很好解決,但是很多人卻不知道這是why,今天師傅給你好好講解一番,悟纖你可得長點心吶,把耳朵帶上。

一、何爲序列化

序列化:把對象轉換爲字節序列的過程。

反序列化:把字節序列恢復爲對象的過程

這裏的概念可能有點不好理解。我們得清楚,JVM是可以處理對象的,在不序列化的情況下,是以對象的狀態在虛擬機中的,這時候,如果我們要將對象保存到緩存Memcached中的話,其中JVM有自己的進程,而Memcached也有自己的進程,兩個不同進程之間要通訊,這就有話說了。

JVM對Memcached說:來我把這個對象傳給你,你給我緩存一下,之後我需要的時候在找你要。

Memcached對JVM說:老兄,不行吶,我不懂得如何處理Java對象吶。

JVM對Memcached說:那我把對象處理爲字節你總可以處理了吧。

Memcached說JVM說:這個可以有,那給我傳字節,我也給你返回字節,你自己轉換爲對象吧。

JVM心裏暗諷:你咋這麼笨吶,好吧,優秀如你,我只能默默看着。

BTW:字節(Byte )是計算機信息技術用於計量存儲容量的一種計量單位,作爲一個單位來處理的一個二進制數字串,是構成信息的一個小單位。

「百度百科」:https://baike.baidu.com/item/字節/1096318?fr=aladdin

悟纖:那師傅都有什麼情況需要進行序列化吶?

師傅:悟纖別急,聽爲師慢慢道來。

二、什麼場景下需要序列化

當你想把內存中的對象狀態保存到一個文件中或者數據庫中時候;

當你想用套接字在網絡上傳送對象的時候;

當你想通過RMI(Remote Method Invocation:遠程方法調用)傳輸對象的時候;

BTW:當要 跨進程跨網絡傳輸對象的時候,這時候基本要序列化了。

悟纖:師傅,你這波BTW騷氣外露耶。

師傅:(咳嗽)悟纖,你頭又癢了吧。

下面舉幾個具體的場景:

(1)對象儲存到緩存中(這個大家經常使用到吧,memcached.set(key,value))。

(2)把一個對象寫到文件中(這個待會咱們會來寫個小栗子)。

三、如何序列化

如何進行序列化吶?常見的有如下幾種方式

3.1 Serializable接口

Serializable最常見就是實現這個序列化標識接口,說它是標識是因爲它的接口源碼中其實什麼都沒有定義,純粹只是爲了標識而使用,一個類只有實現了這個接口,我們才能對它進行序列化。

3.2Externalizable接口

Externalizable這個接口其實是繼承了Serializable,它更加靈活一點,它裏面定義了writeExternal和readExternal兩個方法分別用於序列化和反序列化使用。通過這兩個方法,我們可以自己決定需要序列化那些數據。如果對象中涉及到很少的屬性需要序列化,大多數屬性無需序列化,這種情況使用Externalizable接口是比較靈活的。

3.3 第三方序列化

也可以使用第三方的序列化進行操作,比如:hessian。

hessian它的一個最大特色就是跨語言,hessian提供了一整套的byte[]的寫入規範。這樣其他語言在實現hessian序列化的時候就可以參照這套的標準規範,從而達到不同語言之間的兼容效果,因此hessian的序列化都是圍繞這byte數組來的。

四、Java序列化實操

接下來 爲師 將安排2號技師 給大家來個激情的表演,請大家準備好紙巾。

2號技師要表演什麼呢?她要表演將一個MeiMei對象,寫到文件中,然後在成功的掏出來。

2號技師要開始了,大家準備好了沒有。

環境說明

(1)JDK: 1.8
(2)OS:MAC

4.1 準備一個美眉

我們先準備一個美眉類,MeiMei.java:

package com.kfit;

import java.io.Serializable;

public class MeiMei implements Serializable{
    private static final long serialVersionUID = 1L;
    private long id;
    private String name;//美眉的名稱
    private String cup;//你懂得

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCup() {
        return cup;
    }

    public void setCup(String cup) {
        this.cup = cup;
    }

    @Override
    public String toString() {
        return "MeiMei [id=" + id + ", name=" + name + ", cup=" + cup + "]";
    }
}

BTW:這個類沒啥特殊之處,就是實現了接口Serializable。

4.2 開始表演

2號技師開始你的表演吧,舞臺已經爲你準備好了,這時候,只見2號技師,緩緩走上舞臺,用她婀娜多姿的身材,開始了她精彩的表演......(想想都有點小激動 (๑>◡<๑) )

package com.kfit;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        /*
         * 序列化.
         */
        MeiMei mm = new MeiMei();
        mm.setId(2);
        mm.setName("2號技師");
        mm.setCup("36C");

        ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(new File("/data/tmp/meimei.out")));
        output.writeObject(mm);
        output.close();
        System.out.println("序列化成功:"+mm);

        /*
         * 反序列化
         */
        ObjectInputStream input = new ObjectInputStream(new FileInputStream(new File("/data/tmp/meimei.out")));
        MeiMei mm2 = (MeiMei) input.readObject();
        input.close();
        System.out.println("反序列化成功:"+mm2);
    }

}    

BTW

(1)這裏關注Main方法,就是將對象序列化和反序列化。

(2)ObjectOutputStream代表對象輸出流,ObjectInputStream代表對象輸入流。

(3)流執行之後記得close掉。

Run一下吧,觀察控制檯輸出如下信息:

序列化成功:MeiMei [id=2,name=2號技師, cup=36C]
反序列化成功:MeiMei[id=2, name=2號技師, cup=36C]

如果將MeiMei的implements Serializable去掉就會報如下錯:

Exception inthread "main" java.io.NotSerializableException: com.kfit.MeiMei

       at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)

       atjava.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)

       at com.kfit.Test.main(Test.java:22)

看,技師的衣服不能隨便脫吧,這可鬧大了。

4.3 扒開衣服看看

2號技師慢慢的將衣服….此處省略一萬字。

Java對象寫到文件之後,到底是什麼東東吶,扒開meimei.out看下。

啥,你不知道怎麼扒開,給你一個女的,你還不會了。

在Mac下可以使用Hex Fiend打開二進制文件,下載地址:

https://github.com/ridiculousfish/HexFiend/releases

 

另外一種簡單的方式就是使用vi進行打開(這是Mac哦),主要兩步操作(相信玩Mac的都能看得懂):

(1)首先以二進制方式編輯這個文件
vi -b /data/tmp/meimei.out
(2)使用xxd轉換爲16進制
:%!xxd

扒開之後,可以看到如下文本:

 

 

 

怎麼樣,好看嗎,看不懂,看不懂就對了,衣服又不是你脫的,你怎麼能看明白吶,這種事情還得自己來。

這就是字節,字節可以保存到文件中,也可以保存到數據庫中,取出來字節之後,就可以在轉換爲原來的數據信息了。

4.4 serialVersionUID

美女,serialVersionUID這是啥,你幹嘛帶着,這是我的唯一標識,要脫衣服,就得有serialVersionUID,不然不讓脫哦。

這個serialVersionUID有啥用吶:來驗證版本一致性的。在反序列化時,jvm會把傳來的字節流中的serialVersionUID和本地相應實體類的serialVersionUID進行比較,如果相同就認爲一致,可以進行反序列化,否則出現InvalidCastException異常。

怎麼來驗證下是不是2號技師吶?只需要將脫衣服的代碼刪除即可:

//        MeiMei mm = new MeiMei();
//        mm.setId(2);
//        mm.setName("2號技師");
//        mm.setCup("36C");
//        
//        ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(new File("/data/tmp/meimei.out")));
//        output.writeObject(mm);
//        output.close();
//        System.out.println("序列化成功:"+mm);

 

BTW:將這段序列化的代碼註釋掉。(前提是已經執行成功過一次,已經有meimei.out文件了)

此時執行剩下的代碼(也就是反序列化代碼),會發現此時還是可以匹配到2號技師的,只見控制檯打印如下信息:

反序列化成功:MeiMei[id=2, name=2號技師, cup=36C]

緊接着將MeiMei類中的serialVersionUID修改爲2L。

這時候在摸一下,哦,不,口誤是run一下,完了,2號技師丟了:

Exception in thread "main" java.io.InvalidClassException: com.kfit.MeiMei;
 local class incompatible: stream classdesc serialVersionUID = 1, 
local class serialVersionUID = 2

BTW:由於serialVersionUID不一致,所以匹配失敗,對象反序列化也失敗了。

4.5 這部分不讓摸:static

「內心世界:不敢寫了,在寫就要變成 黃色編程 了,聰明如你,自己領悟吧」

static靜態變量不是對象狀態的一部分,因此它不參與序列化。那麼反序列化之後。我們可以這麼理解,靜態變量的話,不屬於對象的特有的,誰都可以進行改變,所以序列化了之後,在反序列返回來可能就不是那個值了。

4.6 加錢就讓:static-final

如果一個變量修飾爲static final的話,這時候由於final定義的不可被改變,那麼這時候這個屬性就會被持久化了。

4.7 transient:定義臨時變量

transient關鍵字的作用,簡單地說,就是讓某些被修飾的成員屬性變量不被序列化。我們可以在MeiMei類添加一個屬性:

private transient int age;

那麼在序列化的設置age爲18,這時候在運行:

序列化成功:MeiMei [id=2,name=2號技師, cup=36C, age=18]
反序列化成功:MeiMei[id=2, name=2號技師, cup=36C, age=0]

看到沒有age並沒有被序列化,因爲反序列回來之後age爲0了。

五、悟纖小結

今天師傅也講了不少東西了,不知道悟纖你聽懂了多少,你來給大家總結下吧:

(1)序列化和反序列化的定義:就是對象轉換爲字節和字節轉換爲對象的過程。

(2)爲什麼需要序列化:主要是爲了跨進程跨網絡的數據傳輸。

(3)序列化的方式:常用的就是實現接口Serializable

(4)如何讓屬性不序列化:只要將屬性定義爲transient即可。

好,不錯,總結的差不多。今天能記住這些就夠了,好了悟纖,繼續趕路吧,西天還遠着吶。

 

 

白龍馬蹄朝西,馱着唐三藏跟着仨徒弟。

西天取經上大路,一走就是幾萬裏。

白龍馬脖鈴兒急,顛簸唐玄奘小跑仨兄弟,

西天取經不容易,容易幹不成大業績。

 

我就是我,是顏色不一樣的煙火。
我就是我,是與衆不同的小蘋果。

à悟空學院:http://t.cn/Rg3fKJD

學院中有Spring Boot相關的課程!點擊「閱讀原文」進行查看!

SpringBoot視頻:http://t.cn/R3QepWG

Spring Cloud視頻:http://t.cn/R3QeRZc

SpringBoot Shiro視頻:http://t.cn/R3QDMbh

SpringBoot交流平臺:http://t.cn/R3QDhU0

SpringData和JPA視頻:http://t.cn/R1pSojf

SpringSecurity5.0視頻:http://t.cn/EwlLjHh

Sharding-JDBC分庫分表實戰:http://t.cn/E4lpD6e
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章