Java序列化引發(fā)的血案

1、前言

《手冊(cè)》第 9 頁(yè) “OOP 規(guī)約” 部分有一段關(guān)于序列化的約定 1

【強(qiáng)制】當(dāng)序列化類(lèi)新增屬性時(shí)菠剩,請(qǐng)不要修改 serialVersionUID 字段戳表,以避免反序列失敗歌懒;如果完全不兼容升級(jí)啦桌,避免反序列化混亂,那么請(qǐng)修改 serialVersionUID 值。
說(shuō)明:注意 serialVersionUID 值不一致會(huì)拋出序列化運(yùn)行時(shí)異常甫男。

我們應(yīng)該思考下面幾個(gè)問(wèn)題:

  • 序列化和反序列化到底是什么且改?
  • 它的主要使用場(chǎng)景有哪些?
  • Java 序列化常見(jiàn)的方案有哪些板驳?
  • 各種常見(jiàn)序列化方案的區(qū)別有哪些又跛?
  • 實(shí)際的業(yè)務(wù)開(kāi)發(fā)中有哪些坑點(diǎn)?

接下來(lái)將從這幾個(gè)角度去研究這個(gè)問(wèn)題若治。

2. 序列化和反序列化是什么慨蓝?為什么需要它?

序列化是將內(nèi)存中的對(duì)象信息轉(zhuǎn)化成可以存儲(chǔ)或者傳輸?shù)臄?shù)據(jù)到臨時(shí)或永久存儲(chǔ)的過(guò)程端幼。而反序列化正好相反礼烈,是從臨時(shí)或永久存儲(chǔ)中讀取序列化的數(shù)據(jù)并轉(zhuǎn)化成內(nèi)存對(duì)象的過(guò)程。

那么為什么需要序列化和反序列化呢婆跑?

希望大家能夠養(yǎng)成從本源上思考這個(gè)問(wèn)題的思維方式扒磁,即思考它為什么會(huì)出現(xiàn)在讶,而不是單純記憶。

大家可以回憶一下,平時(shí)都是如果將文字文件误债、圖片文件楚午、視頻文件止后、軟件安裝包等傳給小伙伴時(shí)须揣,這些資源在計(jì)算機(jī)中存儲(chǔ)的方式是怎樣的。

進(jìn)而再思考驮审,Java 中的對(duì)象如果需要存儲(chǔ)或者傳輸應(yīng)該通過(guò)什么形式呢鲫寄?

我們都知道,一個(gè)文件通常是一個(gè) m 個(gè)字節(jié)的序列:B0, B1, …, Bk, …, Bm-1疯淫。所有的 I/O 設(shè)備(例如網(wǎng)絡(luò)地来、磁盤(pán)和終端)都被模型化為文件,而所有的輸入和輸出都被當(dāng)作對(duì)應(yīng)文件的讀和寫(xiě)來(lái)執(zhí)行熙掺。2

因此本質(zhì)上講未斑,文本文件,圖片币绩、視頻和安裝包等文件底層都被轉(zhuǎn)化為二進(jìn)制字節(jié)流來(lái)傳輸?shù)睦啵瑢?duì)方得文件就需要對(duì)文件進(jìn)行解析,因此就需要有能夠根據(jù)不同的文件類(lèi)型來(lái)解碼出文件的內(nèi)容的程序缆镣。

大家試想一個(gè)典型的場(chǎng)景:如果要實(shí)現(xiàn) Java 遠(yuǎn)程方法調(diào)用芽突,就需要將調(diào)用結(jié)果通過(guò)網(wǎng)路傳輸給調(diào)用方,如果調(diào)用方和服務(wù)提供方不在一臺(tái)機(jī)器上就很難共享內(nèi)存董瞻,就需要將 Java 對(duì)象進(jìn)行傳輸寞蚌。而想要將 Java 中的對(duì)象進(jìn)行網(wǎng)絡(luò)傳輸或存儲(chǔ)到文件中,就需要將對(duì)象轉(zhuǎn)化為二進(jìn)制字節(jié)流,這就是所謂的序列化挟秤。存儲(chǔ)或傳輸之后必然就需要將二進(jìn)制流讀取并解析成 Java 對(duì)象壹哺,這就是所謂的反序列化。

序列化的主要目的是:方便存儲(chǔ)到文件系統(tǒng)煞聪、數(shù)據(jù)庫(kù)系統(tǒng)或網(wǎng)絡(luò)傳輸?shù)?/strong>斗躏。

實(shí)際開(kāi)發(fā)中常用到序列化和反序列化的場(chǎng)景有:

  • 遠(yuǎn)程方法調(diào)用(RPC)的框架里會(huì)用到序列化逝慧。
  • 將對(duì)象存儲(chǔ)到文件中時(shí)昔脯,需要用到序列化。
  • 將對(duì)象存儲(chǔ)到緩存數(shù)據(jù)庫(kù)(如 Redis)時(shí)需要用到序列化笛臣。
  • 通過(guò)序列化和反序列化的方式實(shí)現(xiàn)對(duì)象的深拷貝云稚。

3. 常見(jiàn)的序列化方式

常見(jiàn)的序列化方式包括 Java 原生序列化、Hessian 序列化沈堡、Kryo 序列化静陈、JSON 序列化等。

3.1 Java 原生序列化

正如前面章節(jié)講到的诞丽,對(duì)于 JDK 中有的類(lèi)鲸拥,最好的學(xué)習(xí)方式之一就是直接看其源碼。

Serializable 的源碼非常簡(jiǎn)單僧免,只有聲明刑赶,沒(méi)有屬性和方法:

// 注釋太長(zhǎng),省略
public interface Serializable {
}

在學(xué)習(xí)源碼注釋之前懂衩,希望大家可以站在設(shè)計(jì)者的角度撞叨,先思考一個(gè)問(wèn)題:如果一個(gè)類(lèi)序列化到文件之后,類(lèi)的結(jié)構(gòu)發(fā)生變化還能否保證正確地反序列化呢浊洞?

答案顯然是不確定的牵敷。

那么如何判斷文件被修改過(guò)了呢? 通撤ㄏ#可以通過(guò)加密算法對(duì)其進(jìn)行簽名枷餐,文件作出任何修改簽名就會(huì)不一致。但是 Java 序列化的場(chǎng)景并不適合使用上述的方案苫亦,因?yàn)轭?lèi)文件的某些位置加個(gè)空格毛肋,換行等符號(hào)類(lèi)的結(jié)構(gòu)沒(méi)有發(fā)生變化,這個(gè)簽名就不應(yīng)該發(fā)生變化著觉。還有一個(gè)類(lèi)新增一個(gè)屬性村生,之前的屬性都是有值的,之前都被序列化到對(duì)象文件中饼丘,有些場(chǎng)景下還希望反序列化時(shí)可以正常解析趁桃,怎么辦呢?

那么是否可以通過(guò)約定一個(gè)唯一的 ID,通過(guò) ID 對(duì)比卫病,不一致就認(rèn)為不可反序列化呢油啤?

實(shí)現(xiàn)序列化接口后,如果開(kāi)發(fā)者不手動(dòng)指定該版本號(hào) ID 怎么辦蟀苛?

既然 Java 序列化場(chǎng)景下的 “簽名” 應(yīng)該根據(jù)類(lèi)的特點(diǎn)生成益咬,我們是否可以不指定序列化版本號(hào)就默認(rèn)根據(jù)類(lèi)名、屬性和函數(shù)等計(jì)算呢帜平?

如果針對(duì)某個(gè)自己定義的類(lèi)幽告,想自定義序列化和反序列化機(jī)制該如何實(shí)現(xiàn)呢?支持嗎裆甩?

帶著這些問(wèn)題我們繼續(xù)看序列化接口的注釋冗锁。


Serializable 的源碼注釋特別長(zhǎng),其核心大致作了下面的說(shuō)明:

Java 原生序列化需要實(shí)現(xiàn) Serializable 接口嗤栓。序列化接口不包含任何方法和屬性等冻河,它只起到序列化標(biāo)識(shí)作用。

一個(gè)類(lèi)實(shí)現(xiàn)序列化接口則其子類(lèi)型也會(huì)繼承序列化能力茉帅,但是實(shí)現(xiàn)序列化接口的類(lèi)中有其他對(duì)象的引用叨叙,則其他對(duì)象也要實(shí)現(xiàn)序列化接口。序列化時(shí)如果拋出 NotSerializableException 異常堪澎,說(shuō)明該對(duì)象沒(méi)有實(shí)現(xiàn) Serializable 接口擂错。

每個(gè)序列化類(lèi)都有一個(gè)叫 serialVersionUID 的版本號(hào),反序列化時(shí)會(huì)校驗(yàn)待反射的類(lèi)的序列化版本號(hào)和加載的序列化字節(jié)流中的版本號(hào)是否一致马昙,如果序列化號(hào)不一致則會(huì)拋出 InvalidClassException 異常刹悴。

強(qiáng)烈推薦每個(gè)序列化類(lèi)都手動(dòng)指定其 serialVersionUID,如果不手動(dòng)指定子房,那么編譯器會(huì)動(dòng)態(tài)生成默認(rèn)的序列化號(hào)证杭,因?yàn)檫@個(gè)默認(rèn)的序列化號(hào)和類(lèi)的特征以及編譯器的實(shí)現(xiàn)都有關(guān)系妒御,很容易在反序列化時(shí)拋出 InvalidClassException 異常乎莉。建議將這個(gè)序列化版本號(hào)聲明為私有奸笤,以避免運(yùn)行時(shí)被修改监右。

實(shí)現(xiàn)序列化接口的類(lèi)可以提供自定義的函數(shù)修改默認(rèn)的序列化和反序列化行為异希。

自定義序列化方法:

private void writeObject(ObjectOutputStream out) throws IOException;

自定義反序列化方法:

private void readObject(ObjectInputStream in) 
  throws IOException, ClassNotFoundException;

通過(guò)自定義這兩個(gè)函數(shù)称簿,可以實(shí)現(xiàn)序列化和反序列化不可序列化的屬性,也可以對(duì)序列化的數(shù)據(jù)進(jìn)行數(shù)據(jù)的加密和解密處理搏色。

3.2 Hessian 序列化

Hessian 是一個(gè)動(dòng)態(tài)類(lèi)型券册,二進(jìn)制序列化烁焙,也是一個(gè)基于對(duì)象傳輸?shù)木W(wǎng)絡(luò)協(xié)議耕赘。Hessian 是一種跨語(yǔ)言的序列化方案操骡,序列化后的字節(jié)數(shù)更少,效率更高岔激。Hessian 序列化會(huì)把復(fù)雜對(duì)象的屬性映射到 Map 中再進(jìn)行序列化虑鼎。

3.3 Kryo 序列化

Kryo 是一個(gè)快速高效的 Java 序列化和克隆工具键痛。Kryo 的目標(biāo)是快速絮短、字節(jié)少和易用丁频。Kryo 還可以自動(dòng)進(jìn)行深拷貝或者淺拷貝扔嵌。Kryo 的拷貝是對(duì)象到對(duì)象的拷貝而不是對(duì)象到字節(jié)痢缎,再?gòu)淖止?jié)到對(duì)象的恢復(fù)独旷。Kryo 為了保證序列化的高效率寥裂,會(huì)提前加載需要的類(lèi)封恰,這會(huì)帶一些消耗,但是這是序列化后文件較小且反序列化非潮畈快的重要原因低飒。

3.4 JSON 序列化

JSON (JavaScript Object Notation) 是一種輕量級(jí)的數(shù)據(jù)交換方式糕档。JSON 序列化是基于 JSON 這種結(jié)構(gòu)來(lái)實(shí)現(xiàn)的速那。JSON 序列化將對(duì)象轉(zhuǎn)化成 JSON 字符串尿背,JSON 反序列化則是將 JSON 字符串轉(zhuǎn)回對(duì)象的過(guò)程榆俺。常用的 JSON 序列化和反序列化的庫(kù)有 Jackson茴晋、GSON回窘、Fastjson 等啡直。

4.Java 常見(jiàn)的序列化方案對(duì)比

我們想要對(duì)比各種序列化方案的優(yōu)劣無(wú)外乎兩點(diǎn)撮执,一點(diǎn)是查資料抒钱,一點(diǎn)是自己寫(xiě)代碼驗(yàn)證谋币。

4.1 Java 原生序列化

Java 序列化的優(yōu)點(diǎn)是:對(duì)對(duì)象的結(jié)構(gòu)描述清晰蕾额,反序列化更安全。主要缺點(diǎn)是:效率低退个,序列化后的二進(jìn)制流較大。

4.2 Hessian 序列化

Hession 序列化二進(jìn)制流較 Java 序列化更小习柠,且序列化和反序列化耗時(shí)更短资溃。但是父類(lèi)和子類(lèi)有相同類(lèi)型屬性時(shí),由于先序列化子類(lèi)再序列化父類(lèi)垫毙,因此反序列化時(shí)子類(lèi)的同名屬性會(huì)被父類(lèi)的值覆蓋掉,開(kāi)發(fā)時(shí)要特別注意這種情況猎拨。

Hession2.0 序列化二進(jìn)制流大小是 Java 序列化的 50%额各,序列化耗時(shí)是 Java 序列化的 30%蛉加,反序列化的耗時(shí)是 Java 序列化的 20%。 3

編寫(xiě)待測(cè)試的類(lèi):

@Data
public class PersonHessian implements Serializable {
    private Long id;
    private String name;
    private Boolean male;
}

@Data
public class Male extends PersonHessian {
    private Long id;
}

編寫(xiě)單測(cè)來(lái)模擬序列化繼承覆蓋問(wèn)題:

/**
 * 驗(yàn)證Hessian序列化繼承覆蓋問(wèn)題
 */
@Test
public void testHessianSerial() throws IOException {
    HessianSerialUtil.writeObject(file, male);
    Male maleGet = HessianSerialUtil.readObject(file);
    // 相等
    Assert.assertEquals(male.getName(), maleGet.getName());
    // male.getId()結(jié)果是1,maleGet.getId()結(jié)果是null
    Assert.assertNull(maleGet.getId());
    Assert.assertNotEquals(male.getId(), maleGet);
}

上述單測(cè)示例驗(yàn)證了:反序列化時(shí)子類(lèi)的同名屬性會(huì)被父類(lèi)的值覆蓋掉的問(wèn)題。

4.3 Kryo 序列化

Kryo 優(yōu)點(diǎn)是:速度快蹂风、序列化后二進(jìn)制流體積小、反序列化超快。但是缺點(diǎn)是:跨語(yǔ)言支持復(fù)雜趋距。注冊(cè)模式序列化更快,但是編程更加復(fù)雜翼雀。

4.4 JSON 序列化

JSON 序列化的優(yōu)勢(shì)在于可讀性更強(qiáng)肋殴。主要缺點(diǎn)是:沒(méi)有攜帶類(lèi)型信息官地,只有提供了準(zhǔn)確的類(lèi)型信息才能準(zhǔn)確地進(jìn)行反序列化赤炒,這點(diǎn)也特別容易引發(fā)線(xiàn)上問(wèn)題。

下面給出使用 Gson 框架模擬 JSON 序列化時(shí)遇到的反序列化問(wèn)題的示例代碼:

/**
 * 驗(yàn)證GSON序列化類(lèi)型錯(cuò)誤
 */
@Test
public void testGSON() {
    Map<String, Object> map = new HashMap<>();
    final String name = "name";
    final String id = "id";
    map.put(name, "張三");
    map.put(id, 20L);

    String jsonString = GSONSerialUtil.getJsonString(map);
    Map<String, Object> mapGSON = GSONSerialUtil.parseJson(jsonString, Map.class);
    // 正確
    Assert.assertEquals(map.get(name), mapGSON.get(name));
    // 不等  map.get(id)為L(zhǎng)ong類(lèi)型 mapGSON.get(id)為Double類(lèi)型
    Assert.assertNotEquals(map.get(id).getClass(), mapGSON.get(id).getClass());
    Assert.assertNotEquals(map.get(id), mapGSON.get(id));
}

下面給出使用 fastjson 模擬 JSON 反序列化問(wèn)題的示例代碼:

/**
 * 驗(yàn)證FatJson序列化類(lèi)型錯(cuò)誤
 */
@Test
public void testFastJson() {
    Map<String, Object> map = new HashMap<>();
    final String name = "name";
    final String id = "id";
    map.put(name, "張三");
    map.put(id, 20L);

    String fastJsonString = FastJsonUtil.getJsonString(map);
    Map<String, Object> mapFastJson = FastJsonUtil.parseJson(fastJsonString, Map.class);

    // 正確
    Assert.assertEquals(map.get(name), mapFastJson.get(name));
    // 錯(cuò)誤  map.get(id)為L(zhǎng)ong類(lèi)型 mapFastJson.get(id)為Integer類(lèi)型
    Assert.assertNotEquals(map.get(id).getClass(), mapFastJson.get(id).getClass());
    Assert.assertNotEquals(map.get(id), mapFastJson.get(id));
}

大家還可以通過(guò)單元測(cè)試構(gòu)造大量復(fù)雜對(duì)象對(duì)比各種序列化方式或框架的效率巡通。

如定義下列測(cè)試類(lèi)為 User誊锭,包括以下多種類(lèi)型的屬性:

@Data
public class User implements Serializable {
    private Long id;
    private String name;
    private Integer age;
    private Boolean sex;
    private String nickName;
    private Date birthDay;
    private Double salary;
}

4.5 各種常見(jiàn)的序列化性能排序

實(shí)驗(yàn)的版本:kryo-shaded 使用 4.0.2 版本叉讥,gson 使用 2.8.5 版本,hessian 用 4.0.62 版本。

實(shí)驗(yàn)的數(shù)據(jù):構(gòu)造 50 萬(wàn) User 對(duì)象運(yùn)行多次六孵。

大致得出一個(gè)結(jié)論:

  • 從二進(jìn)制流大小來(lái)講:JSON 序列化 > Java 序列化 > Hessian2 序列化 > Kryo 序列化 > Kryo 序列化注冊(cè)模式劫窒;
  • 從序列化耗時(shí)而言來(lái)講:GSON 序列化 > Java 序列化 > Kryo 序列化 > Hessian2 序列化 > Kryo 序列化注冊(cè)模式孕索;
  • 從反序列化耗時(shí)而言來(lái)講:GSON 序列化 > Java 序列化 > Hessian2 序列化 > Kryo 序列化注冊(cè)模式 > Kryo 序列化散怖;
  • 從總耗時(shí)而言:Kryo 序列化注冊(cè)模式耗時(shí)最短。

注:由于所用的序列化框架版本不同欠动,對(duì)象的復(fù)雜程度不同,環(huán)境和計(jì)算機(jī)性能差異等原因結(jié)果可能會(huì)有出入碗脊。

5. 序列化引發(fā)的一個(gè)血案

接下來(lái)我們看下面的一個(gè)案例:

前端調(diào)用服務(wù) A祈坠,服務(wù) A 調(diào)用服務(wù) B,服務(wù) B 首次接到請(qǐng)求會(huì)查 DB躺同,然后緩存到 Redis(緩存 1 個(gè)小時(shí))捎谨。服務(wù) A 根據(jù)服務(wù) B 返回的數(shù)據(jù)后執(zhí)行一些處理邏輯涛救,處理后形成新的對(duì)象存到 Redis(緩存 2 個(gè)小時(shí))棵红。

服務(wù) A 通過(guò) Dubbo 來(lái)調(diào)用服務(wù) B致板,A 和 B 之間數(shù)據(jù)通過(guò) Map<String,Object> 類(lèi)型傳輸,服務(wù) B 使用 Fastjson 來(lái)實(shí)現(xiàn) JSON 的序列化和反序列化御毅。

服務(wù) B 的接口返回的 Map 值中存在一個(gè) Long 類(lèi)型的 id 字段,服務(wù) A 獲取到 Map 柔袁,取出 id 字段并強(qiáng)轉(zhuǎn)為 Long 類(lèi)型使用插掂。

執(zhí)行的流程如下:


通過(guò)分析我們發(fā)現(xiàn),服務(wù) A 和服務(wù) B 的 RPC 調(diào)用使用 Java 序列化,因此類(lèi)型信息不會(huì)丟失捐凭。

但是由于服務(wù) B 采用 JSON 序列化進(jìn)行緩存垦梆,第一次訪(fǎng)問(wèn)沒(méi)啥問(wèn)題,其執(zhí)行流程如下:


圖片描述

如果服務(wù) A 開(kāi)啟了緩存,服務(wù) A 在第一次請(qǐng)求服務(wù) B 后,緩存了運(yùn)算結(jié)果晃琳,且服務(wù) A 緩存時(shí)間比服務(wù) B 長(zhǎng)人灼,因此不會(huì)出現(xiàn)錯(cuò)誤适贸。

圖片描述

如果服務(wù) A 不開(kāi)啟緩存蕊肥,服務(wù) A 會(huì)請(qǐng)求服務(wù) B ,由于首次請(qǐng)求時(shí),服務(wù) B 已經(jīng)緩存了數(shù)據(jù),服務(wù) B 從 Redis(B)中反序列化得到 Map惊完。流程如下圖所示:

然而問(wèn)題來(lái)了: 服務(wù) A 從 Map 取出此 Id 字段小槐,強(qiáng)轉(zhuǎn)為 Long 時(shí)會(huì)出現(xiàn)類(lèi)型轉(zhuǎn)換異常。

最后定位到原因是 Json 反序列化 Map 時(shí)如果原始值小于 Int 最大值凿跳,反序列化后原本為 Long 類(lèi)型的字段,變?yōu)榱?Integer 類(lèi)型茧彤,服務(wù) B 的同學(xué)緊急修復(fù)疆栏。

服務(wù) A 開(kāi)啟緩存時(shí), 雖然采用了 JSON 序列化存入緩存壁顶,但是采用 DTO 對(duì)象而不是 Map 來(lái)存放屬性若专,所以 JSON 反序列化沒(méi)有問(wèn)題。

因此大家使用二方或者三方服務(wù)時(shí)调衰,當(dāng)對(duì)方返回的是 Map<String,Object> 類(lèi)型的數(shù)據(jù)時(shí)要特別注意這個(gè)問(wèn)題

作為服務(wù)提供方米酬,可以采用 JDK 或者 Hessian 等序列化方式趋箩;

作為服務(wù)的使用方琼懊,我們不要從 Map 中一個(gè)字段一個(gè)字段獲取和轉(zhuǎn)換爬早,可以使用 JSON 庫(kù)直接將 Map 映射成所需的對(duì)象启妹,這樣做不僅代碼更簡(jiǎn)潔還可以避免強(qiáng)轉(zhuǎn)失敗。

代碼示例:

@Test
public void testFastJsonObject() {
    Map<String, Object> map = new HashMap<>();
    final String name = "name";
    final String id = "id";
    map.put(name, "張三");
    map.put(id, 20L);

    String fastJsonString = FastJsonUtil.getJsonString(map);
    // 模擬拿到服務(wù)B的數(shù)據(jù)
    Map<String, Object> mapFastJson = FastJsonUtil.parseJson(fastJsonString,map.getClass());
    // 轉(zhuǎn)成強(qiáng)類(lèi)型屬性的對(duì)象而不是使用map 單個(gè)取值
    User user = new JSONObject(mapFastJson).toJavaObject(User.class);
    // 正確
    Assert.assertEquals(map.get(name), user.getName());
    // 正確
    Assert.assertEquals(map.get(id), user.getId());
}

6. 總結(jié)

本節(jié)的主要講解了序列化的主要概念桨啃、主要實(shí)現(xiàn)方式檬输,以及序列化和反序列化的幾個(gè)坑點(diǎn),希望大家在實(shí)際業(yè)務(wù)開(kāi)發(fā)中能夠注意這些細(xì)節(jié)丧慈,避免趟坑。

下一節(jié)將講述淺拷貝和深拷貝的相關(guān)知識(shí)鹃愤。

7. 課后題

給出一個(gè) PersonTransit 類(lèi)完域,一個(gè) Address 類(lèi),假設(shè) Address 是其它 jar 包中的類(lèi)凹耙,沒(méi)實(shí)現(xiàn)序列化接口肠仪。請(qǐng)使用今天講述的自定義的函數(shù) writeObjectreadObject 函數(shù)實(shí)現(xiàn) PersonTransit 對(duì)象的序列化,要求反序列化后 address 的值正常藤韵。

@Data
public class PersonTransit implements Serializable {

    private Long id;
    private String name;
    private Boolean male;
    private List<PersonTransit> friends;
    private Address address;
}

@Data
@AllArgsConstructor
public class Address {
    private String detail;
  }

參考資料


  1. 阿里巴巴與 Java 社區(qū)開(kāi)發(fā)者.《 Java 開(kāi)發(fā)手冊(cè) 1.5.0》華山版. 2019. 9 ??

  2. [美] Randal E.Bryant/ David O’Hallaron.《深入理解計(jì)算機(jī)系統(tǒng)》. [譯] 龔奕利泽艘,賀蓮。機(jī)械工業(yè)出版社. 2016 ??

  3. 楊冠寶匹涮。高海慧.《碼出高效:Java 開(kāi)發(fā)手冊(cè)》. 電子工業(yè)出版社. 2018 ??

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末喜每,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子枫笛,更是在濱河造成了極大的恐慌刚照,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啊楚,死亡現(xiàn)場(chǎng)離奇詭異浑彰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)郭变,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)饵较,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拍嵌,“玉大人循诉,你說(shuō)我怎么就攤上這事”吩椋” “怎么了划纽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)靖避。 經(jīng)常有香客問(wèn)我比默,道長(zhǎng),這世上最難降的妖魔是什么篡九? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任醋奠,我火速辦了婚禮伊佃,結(jié)果婚禮上沛善,老公的妹妹穿的比我還像新娘。我一直安慰自己路呜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著抵屿,像睡著了一般捅位。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上艇搀,一...
    開(kāi)封第一講書(shū)人閱讀 51,775評(píng)論 1 307
  • 那天焰雕,我揣著相機(jī)與錄音,去河邊找鬼矩屁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泊脐,可吹牛的內(nèi)容都是我干的烁峭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼缩挑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼棍现!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起己肮,我...
    開(kāi)封第一講書(shū)人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤悲关,失蹤者是張志新(化名)和其女友劉穎娄柳,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體秫筏,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挎挖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年蕉朵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片始衅。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汛闸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诸老,到底是詐尸還是另有隱情,我是刑警寧澤吮廉,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布畸肆,位于F島的核電站,受9級(jí)特大地震影響轴脐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恬涧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一碴巾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧提揍,春花似錦、人聲如沸劳跃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)杉武。三九已至,卻和暖如春轻抱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工夭问, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留曹铃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓秘血,卻偏偏與公主長(zhǎng)得像评甜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子忍坷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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

  • 1佩研、引言 《手冊(cè)》第 9 頁(yè) “OOP 規(guī)約” 部分有一段關(guān)于序列化的約定 1: 【強(qiáng)制】當(dāng)序列化類(lèi)新增屬性時(shí),請(qǐng)...
    chen_chen_chen_閱讀 484評(píng)論 0 1
  • JAVA序列化機(jī)制的深入研究 對(duì)象序列化的最主要的用處就是在傳遞,和保存對(duì)象(object)的時(shí)候,保證對(duì)象的完整...
    時(shí)待吾閱讀 10,872評(píng)論 0 24
  • 最近在設(shè)計(jì)一個(gè)RPC框架晰骑,需要處理序列化的問(wèn)題绊序。有很多種序列化協(xié)議可以選擇秽荞,比如Java原生的序列化協(xié)議岗宣,Prot...
    逗逼程序員閱讀 1,266評(píng)論 0 1
  • 概念 Java中號(hào)稱(chēng)一切皆是對(duì)象,在Java程序運(yùn)行過(guò)程中耗式,都是借助對(duì)象來(lái)完成一系列我們想要的操作。但是對(duì)象...
    still_loving閱讀 2,134評(píng)論 0 1
  • Dubbo + Kryo 實(shí)現(xiàn)高速序列化 本節(jié)視頻 【視頻】Dubbo 實(shí)現(xiàn)微服務(wù)架構(gòu)-Dubbo-使用 Kryo...
    擼帝閱讀 4,191評(píng)論 0 10