題外話:
從事IT要學(xué)習(xí)的東西太多了,有時(shí)候會(huì)比較浮躁,因?yàn)橐獙W(xué)的東西太多但又無(wú)從下手,甚至有很多基礎(chǔ)都還沒(méi)有深入學(xué)習(xí),這個(gè)時(shí)候應(yīng)當(dāng)靜下心來(lái),正所謂不忘初心,方能始終,之前一直聽(tīng)說(shuō)過(guò)序列化,但也沒(méi)有去深入一點(diǎn)點(diǎn)的了解過(guò),這個(gè)時(shí)候,就當(dāng)好好鞏固下了~
java序列化
常被稱為持久化,將其寫入磁盤中。
對(duì)于一個(gè)存在于jvm的對(duì)象來(lái)說(shuō),內(nèi)部的狀態(tài)保存在內(nèi)存中,當(dāng)jvm停止時(shí)這些狀態(tài)就丟失了,但有些時(shí)候?qū)ο蟮膬?nèi)部是需要持久保存的,對(duì)象序列化機(jī)制(object serialization)是Java語(yǔ)言內(nèi)建的一種對(duì)象持久化方式,可以很容易的在JVM中的活動(dòng)對(duì)象和字節(jié)數(shù)組(流)之間進(jìn)行轉(zhuǎn)換,該機(jī)制中對(duì)象可以表示為字節(jié)序列,該字節(jié)序列包括該對(duì)象的數(shù)據(jù),有關(guān)對(duì)象的類型的信息和存儲(chǔ)在對(duì)象中數(shù)據(jù)的類型。
數(shù)據(jù)序列化就是將對(duì)象或者數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化成特定的格式,使其可在網(wǎng)絡(luò)中傳輸,或者可存儲(chǔ)在內(nèi)存或者文件中。反序列化則是相反的操作,將對(duì)象從序列化數(shù)據(jù)中還原出來(lái)。而對(duì)象序列化后的數(shù)據(jù)格式可以是二進(jìn)制,可以是XML,也可以是JSON等任何格式。
【整個(gè)過(guò)程在jvm獨(dú)立的,在一個(gè)平臺(tái)上序列化的對(duì)象可以在另外的平臺(tái)反序列化】
java類序列化的條件:
1.該類必須實(shí)現(xiàn) java.io.Serializable接口。
2.該類的所有屬性必須是可序列化的。如果有一個(gè)屬性不是可序列化的,則該屬性必須注明是短暫的。如果你想知道一個(gè) Java 標(biāo)準(zhǔn)類是否是可序列化的,請(qǐng)查看該類的文檔。檢驗(yàn)一個(gè)類的實(shí)例是否能序列化十分簡(jiǎn)單, 只需要查看該類有沒(méi)有實(shí)現(xiàn) java.io.Serializable接口。
為什么序列化
1.將結(jié)構(gòu)化的對(duì)象變?yōu)闊o(wú)結(jié)構(gòu)的字節(jié)流,存儲(chǔ)對(duì)象在存儲(chǔ)介質(zhì)中,方便下次使用可以快捷獲取,便于數(shù)據(jù)傳輸。
2.序列化的過(guò)程通俗講,就是一個(gè)“freeze”的過(guò)程,它將一個(gè)對(duì)象freeze住,然后進(jìn)行存儲(chǔ),等到再次需要的時(shí)候,再將這個(gè)對(duì)象de-freeze就可以立即使用。
jdk內(nèi)置序列化
java對(duì)序列化提供了很好的支持,當(dāng)一個(gè)對(duì)象實(shí)現(xiàn)了Serilizable接口,這個(gè)對(duì)象就可以被序列化,我們不關(guān)心其內(nèi)在的原理,只需要了解這個(gè)類實(shí)現(xiàn)了Serilizable接口,這個(gè)類的所有屬性和方法都會(huì)自動(dòng)序列化。可以說(shuō)Serilizable只是一個(gè)標(biāo)識(shí),實(shí)際的序列化和反序列化工作是通過(guò)java.io.ObjectOuputStream和java.io.ObjectInputStream來(lái)完成的。ObjectOutputStream的writeObject方法可以把一個(gè)Java對(duì)象寫入到流中,ObjectInputStream的readObject方法可以從流中讀取一個(gè)Java對(duì)象。
transient關(guān)鍵字
在實(shí)際開(kāi)發(fā)過(guò)程中可能遇到說(shuō)一個(gè)對(duì)象中的屬性有些需要序列化有些則不用,比如說(shuō)一個(gè)用戶有些敏感信息(密碼,銀行卡號(hào)),為了安全考慮不需要在網(wǎng)絡(luò)操作中被傳輸。
這種時(shí)候使用transient可以使對(duì)應(yīng)的屬性不被寫入磁盤持久化。換句話說(shuō),這個(gè)對(duì)象的生命周期僅存在調(diào)用者的內(nèi)存中而不被持久化在硬盤中或者網(wǎng)絡(luò)傳輸。
//使用例子
package serializable;
import java.io.*;
public class TransientTest implements Serializable{
static class UserInfo implements Serializable {
private String name; //此處加static反序列化后仍能取到是因?yàn)閟tatic修飾的變量存在jvm內(nèi)存中
private transient String psw;//transient 只能修飾變量(屬性)
public UserInfo(String name, String psw) {
this.name = name;
this.psw = psw;
}
public String toString() {
return "name=" + name + ", psw=" + psw;
}
}
public static void main(String[] args) {
UserInfo userInfo = new UserInfo("張三", "123456");
System.out.println(userInfo);
try {
// 序列化將對(duì)象屬性寫入到UserInfo.txt文件中,被設(shè)置為transient的屬性沒(méi)有被序列
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt"));
o.writeObject(userInfo);
o.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
// 重新讀取序列化內(nèi)容
ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt"));
UserInfo readUserInfo = (UserInfo) in.readObject();
System.out.println(readUserInfo.toString()); //修飾transient關(guān)鍵字的屬性打印為null
} catch (Exception e) {
e.printStackTrace();
}
}
}
Externalizable接口
在java中,對(duì)象的序列化可以通過(guò)兩種接口實(shí)現(xiàn),除了Serilizable接口,還有就是Externalizable接口。
1.若實(shí)現(xiàn)Serializable,則所有序列化將會(huì)自動(dòng)執(zhí)行。
2.若實(shí)現(xiàn)Externalizable,序列化的過(guò)程需手動(dòng)執(zhí)行,需要在writeExternal方法中進(jìn)行手工指定所要序列化的變量,與是否被transient修飾無(wú)關(guān)。
import java.io.*;
/**
* Created by LJW on 2018/5/28.
* Externalizable接口測(cè)試
*/
public class TestExternalizable implements Externalizable {
private transient String content = "就算被transient修飾,但如果實(shí)現(xiàn)的是Externalizable接口,我還是可能被序列化";
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
public static void main(String[] args) throws Exception {
TestExternalizable et = new TestExternalizable();
//將TestExternalizable序列化到test.txt文件中
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
new File("test.txt")));
out.writeObject(et);
//反序列化test.txt中的信息
ObjectInput in = new ObjectInputStream(new FileInputStream(new File(
"test.txt")));
et = (TestExternalizable) in.readObject();
System.out.println(et.content);//成功打印content內(nèi)容而不是null,說(shuō)明反序列化有取到被transient修飾的變量屬性
out.close();
in.close();
}
}
serialVersionUID
在查看jdk源碼的時(shí)候,經(jīng)常看到這種代碼
private static final long serialVersionUID = 2877471301981509474L; //xxxL
一個(gè)類如果使用了java.io.Serializable接口,在序列化到文件時(shí)會(huì)自動(dòng)生成一個(gè)serialVersionUID,用于對(duì)類進(jìn)行版本控制(通過(guò)判斷實(shí)體類的serialVersionUID來(lái)驗(yàn)證版本一致性的。在進(jìn)行反序列化時(shí),JVM會(huì)把傳來(lái)的字節(jié)流中的serialVersionUID與本地相應(yīng)實(shí)體類的serialVersionUID進(jìn)行比較,如果相同就認(rèn)為是一致的,可以進(jìn)行反序列化,否則就會(huì)出現(xiàn)序列化版本不一致InvalidCalssException的異常)
如何生成
Intellij IDEA可以自動(dòng)為serializable的類生成一個(gè)serialVersionUID。
File->Preferences->Inspections->Serializationissues,將其展開(kāi)后將serialzable class without "serialVersionUID"打上勾;
之后雙擊下class類名 ALT+ENTER即可生成隨機(jī)的serialVersionUID
總結(jié)
- Java序列化就是把對(duì)象轉(zhuǎn)換成字節(jié)序列,而Java反序列化就是把字節(jié)序列還原成Java對(duì)象,在java中可以通過(guò)實(shí)現(xiàn)Serializable和Externalizable兩種接口實(shí)現(xiàn)序列化。
- 采用Java序列化與反序列化技術(shù),一是可以實(shí)現(xiàn)數(shù)據(jù)的持久化,在MVC模式中很有用;二是可以對(duì)象數(shù)據(jù)的遠(yuǎn)程通信,序列化用于通信,服務(wù)端把數(shù)據(jù)序列化發(fā)送到客戶端。客戶端收到數(shù)據(jù)反序列化對(duì)數(shù)據(jù)操作。
- 序列化的好處:通過(guò)序列化可以把數(shù)據(jù)永久保存在硬盤上(通常放在文件里)
- transient關(guān)鍵字只能修飾屬性,被transient修飾的屬性將不會(huì)被序列化(這邊的前提是實(shí)現(xiàn)Serializable接口,還有需注意被static修飾的屬性也無(wú)法被序列化,static修飾的變量存在jvm內(nèi)存中,如果反序列化后得到static修飾的屬性,是從jvm取而不是反序列化后得到)。
- serialVersionUID主要用于反序列化的時(shí)候驗(yàn)證版本的一致性,常在jdk,各種jar包中使用。