日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
你知道為什么序列化要寫(xiě)serialVersionUID嗎?

?前言

java中的序列化可能大家像我一樣都停留在實(shí)現(xiàn)Serializable?接口上,對(duì)于它里面的一些核心機(jī)制沒(méi)有深入了解過(guò)。直到最近在項(xiàng)目中踩了一個(gè)坑,就是序列化對(duì)象添加一個(gè)字段以后,使用方系統(tǒng)報(bào)了反序列化失敗,原因是我們雙方的序列化對(duì)象沒(méi)有加上serialVersionUID,那你們知道下面幾個(gè)問(wèn)題嗎:

10年積累的成都做網(wǎng)站、網(wǎng)站建設(shè)經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問(wèn)題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站設(shè)計(jì)制作后付款的網(wǎng)站建設(shè)流程,更有羅田免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。

  1. 序列化對(duì)象中的serialVersionUID 是干嘛用的?
  2. 如何修改默認(rèn)的序列化機(jī)制?
  3. 如何使用序列化的方式克隆對(duì)象?

對(duì)象序列化和反序列化機(jī)制

序列化: 將對(duì)象轉(zhuǎn)成二進(jìn)制寫(xiě)到輸出流的過(guò)程。

反序列化: 通過(guò)輸入流讀回二進(jìn)制轉(zhuǎn)成對(duì)象的過(guò)程。

通過(guò)對(duì)象的序列化和反序列化機(jī)制可以實(shí)現(xiàn)對(duì)象在網(wǎng)絡(luò)之間傳輸。

在Java中,如果一個(gè)對(duì)象要想實(shí)現(xiàn)序列化,必須要實(shí)現(xiàn)下面兩個(gè)接口之一:

  • Serializable 接口
  • Externalizable 接口

這里我們先講解常用的Serializable 接口。

writeObject序列化過(guò)程栗子:

@Test
public void testSerializable() throws FileNotFoundException {
User user = new User("alvin", 19);
// 文件輸出流
FileOutputStream bout = new FileOutputStream("user.dat");
try (ObjectOutputStream out = new ObjectOutputStream(bout)) {
// 序列化
out.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
}
}


@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {

private String username;

private Integer age;
}

結(jié)果:

readObject反序列化栗子:

現(xiàn)在模擬另外一個(gè)系統(tǒng)需要反序列化user.dat

@Test
public void testDeSerializable() throws FileNotFoundException {
User user = null;
// 寫(xiě)到內(nèi)存中,當(dāng)然也可以寫(xiě)到文件中
FileInputStream fis = new FileInputStream("user.dat");
try (ObjectInputStream in = new ObjectInputStream(fis)) {
// 反序列化 readObject
user = (User) in.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}

Assert.assertEquals("alvin", user.getUsername());
}

如果User類不實(shí)現(xiàn)Serializable接口, 那會(huì)怎么樣?

當(dāng)然是報(bào)錯(cuò)了,如下圖:

小結(jié):

一個(gè)對(duì)象想要被序列化,那么它的類就要實(shí)現(xiàn)此接口或者它的子接口。

修改默認(rèn)的序列化機(jī)制

默認(rèn)的情況下,如果實(shí)現(xiàn)了Serializable接口的對(duì)象進(jìn)行序列化的時(shí)候,默認(rèn)會(huì)將全部的數(shù)據(jù)域,也就是成員變量進(jìn)行序列化輸出,那往往有時(shí)候并不需要這樣,有什么方法可以修改序列化機(jī)制呢?下面提供3種方式。

使用transient關(guān)鍵字

將成員變量標(biāo)記成transient,那么在序列化的過(guò)程中這些數(shù)據(jù)域會(huì)被跳過(guò),如下圖所示:

這是一種最簡(jiǎn)單的方式,但是不夠靈活。

自定義readObject、writeObject方法

序列化類中可以通過(guò)定義下面簽名的方法:

  • private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException
  • private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException

只要類中有這兩個(gè)簽名的方法,那么就不會(huì)調(diào)用默認(rèn)的序列化,取而代之調(diào)用這些方法。

本例我們舉個(gè)jdk中的例子,ArrayList就實(shí)現(xiàn)了這兩個(gè)方法,重寫(xiě)了序列化機(jī)制。

主要原因ArrayList底層的數(shù)組通常會(huì)預(yù)留一些容量,等容量不足時(shí)再擴(kuò)充容量,那么有些空間可能就沒(méi)有實(shí)際存儲(chǔ)元素,采用自定義方式實(shí)現(xiàn)序列化時(shí),就可以保證只序列化實(shí)際存儲(chǔ)的那些元素,而不是整個(gè)數(shù)組,從而節(jié)省空間和時(shí)間。

實(shí)現(xiàn)Externalizable接口

Externalizable?接口想必大家很少用到,它是Serializable?接口的子類,用戶要實(shí)現(xiàn)的writeExternal()和readExternal() 方法,用來(lái)決定如何序列化和反序列化。

因?yàn)樾蛄谢头葱蛄谢椒ㄐ枰约簩?shí)現(xiàn),因此可以指定序列化哪些屬性,而transient在這里無(wú)效。

對(duì)Externalizable?對(duì)象反序列化時(shí),會(huì)先調(diào)用類的無(wú)參構(gòu)造方法,這是有別于默認(rèn)反序列方式的。如果把類的不帶參數(shù)的構(gòu)造方法刪除,或者把該構(gòu)造方法的訪問(wèn)權(quán)限設(shè)置為private?、默認(rèn)或protected?級(jí)別,會(huì)拋出java.io.InvalidException: no valid constructor?異常,因此Externalizable?對(duì)象必須有默認(rèn)構(gòu)造函數(shù),而且必需是public的。

舉例說(shuō)明:

public class User2 implements Externalizable {

private String username;

private Integer age;

@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(username);
out.writeInt(age);
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
username = in.readUTF();
age = in.readInt();
}
}

serialVersionUID的作用

這就回到概述中提到的項(xiàng)目中遇到的問(wèn)題,現(xiàn)在簡(jiǎn)要描述下:

A系統(tǒng)中的序列化對(duì)象User用的最新版本如下:

B系統(tǒng)中反序列化的對(duì)象,還是老的User版本如下:

這時(shí)候A系統(tǒng)生成的序列化文件,交給B系統(tǒng)反序列化時(shí),出錯(cuò)了, 如下圖:

原因:

類定義發(fā)生了變化,比如添加、刪除、修改類中的數(shù)據(jù)域后,它的唯一標(biāo)記符或者稱為SHA指紋、或者理解為serialVersionUID都會(huì)發(fā)生變化,這個(gè)值會(huì)保存在序列化二進(jìn)制中,如果反序列化過(guò)程發(fā)現(xiàn)對(duì)不上,就會(huì)報(bào)錯(cuò),如上圖所示。

那么如何處理呢?

這時(shí)候,我們?nèi)绻X(jué)得這個(gè)序列化對(duì)象是可以兼容的,那么可以自定義一個(gè)serialVersionUID的靜態(tài)成員變量,它就不會(huì)自動(dòng)生成,而是直接用這個(gè)值,如下圖:

使用序列化clone

clone大家都知道吧,在深拷貝的時(shí)候編碼還是很麻煩的,借用序列化機(jī)制可以實(shí)現(xiàn)深拷貝。做法很簡(jiǎn)單,就是將對(duì)象序列化到輸出流中,然后讀回。

public class SerialCloneable implements Cloneable, Serializable {

@Override
public Object clone() throws CloneNotSupportedException {
try {
// 保存到字節(jié)數(shù)組流
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try(ObjectOutputStream out = new ObjectOutputStream(bout)) {
out.writeObject(this);
}
// 讀取
try(InputStream bin = new ByteArrayInputStream(bout.toByteArray())) {
ObjectInputStream in = new ObjectInputStream(bin);
return in.readObject();
}
} catch (IOException | ClassNotFoundException e) {
CloneNotSupportedException e2 = new CloneNotSupportedException();
e2.initCause(e);
throw e2;
}
}
}

注意一點(diǎn),這種方式性能不高,通常比顯示構(gòu)建、復(fù)制數(shù)據(jù)要慢不少。

總結(jié)

本文講解了序列化的一些核心機(jī)制,不再簡(jiǎn)簡(jiǎn)單單的停留在序列化就是實(shí)現(xiàn)Serializable接口了,希望能幫助到大家。


新聞標(biāo)題:你知道為什么序列化要寫(xiě)serialVersionUID嗎?
分享鏈接:http://www.dlmjj.cn/article/coheppd.html