優美格式地址: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