每日一博 - Java序列化一二事儿
文章目錄
- what
- Why
- 作用
- 常用API
- java.io.Serializable
- java.io.Externalizable
- java.io.ObjectOutputStream
- java.io.ObjectInputStream
- Code
- 實(shí)現(xiàn)Serializable接口
- ObjectOutputStream#writeObject 實(shí)現(xiàn)序列化
- ObjectInputStream#readObject方法實(shí)現(xiàn)反序列化
- 序列化底層實(shí)現(xiàn)分析
- writeObject(Object)
- FAQ
what
把Java對(duì)象轉(zhuǎn)換為字節(jié)序列的過(guò)程,-----------> 序列化
把字節(jié)序列恢復(fù)為Java對(duì)象的過(guò)程,-----------> 反序列化
Why
我們知道,Java對(duì)象是運(yùn)行在JVM的堆內(nèi)存中的,如果JVM停止后,對(duì)象也就不復(fù)存在了。
如果想在JVM停止后,把這些對(duì)象保存到磁盤(pán)或者通過(guò)網(wǎng)絡(luò)傳輸?shù)搅硪贿h(yuǎn)程機(jī)器,怎么辦呢?------------------------------------就要把這些對(duì)象轉(zhuǎn)化為字節(jié)數(shù)組,這個(gè)過(guò)程就是序列化 .
作用
序列化使得對(duì)象可以脫離程序運(yùn)行而獨(dú)立存在,它主要有兩種用途:
- 序列化機(jī)制可以讓對(duì)象地保存到磁盤(pán)上,減輕內(nèi)存壓力的同時(shí),也起了持久化的作用;
比如 Web服務(wù)器中的Session對(duì)象,當(dāng)有 10+萬(wàn)用戶并發(fā)訪問(wèn)的,就有可能出現(xiàn)10萬(wàn)個(gè)Session對(duì)象,內(nèi)存可能消化不良,于是Web容器就會(huì)把一些seesion先序列化到硬盤(pán)中,等要用了,再把保存在硬盤(pán)中的對(duì)象還原到內(nèi)存中 【僅舉例,實(shí)際工作中并不會(huì)這么干】
- 序列化機(jī)制讓Java對(duì)象在網(wǎng)絡(luò)中傳輸變得更加容易
在使用Dubbo遠(yuǎn)程調(diào)用服務(wù)框架時(shí),需要把傳輸?shù)腏ava對(duì)象實(shí)現(xiàn)Serializable接口,即讓Java對(duì)象序列化,因?yàn)檫@樣才能讓對(duì)象在網(wǎng)絡(luò)上傳輸。
常用API
java.io.Serializable
Serializable接口是一個(gè)標(biāo)記接口,沒(méi)有方法或字段。一旦實(shí)現(xiàn)了此接口,就標(biāo)志該類的對(duì)象就是可序列化的。
public interface Serializable { }java.io.Externalizable
Externalizable繼承了Serializable接口,還定義了兩個(gè)抽象方法:writeExternal()和readExternal(),如果開(kāi)發(fā)人員使用Externalizable來(lái)實(shí)現(xiàn)序列化和反序列化,需要重寫(xiě)writeExternal()和readExternal()方法
public interface Externalizable extends java.io.Serializable {void writeExternal(ObjectOutput out) throws IOException;void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; }java.io.ObjectOutputStream
表示對(duì)象輸出流,它的writeObject(Object obj)方法可以對(duì)指定obj對(duì)象參數(shù)進(jìn)行序列化,再把得到的字節(jié)序列寫(xiě)到一個(gè)目標(biāo)輸出流中。
java.io.ObjectInputStream
表示對(duì)象輸入流,它的readObject()方法,從輸入流中讀取到字節(jié)序列,反序列化成為一個(gè)對(duì)象,最后將其返回。
Code
實(shí)現(xiàn)Serializable接口
import java.io.Serializable;/*** @author 小工匠* @version 1.0* @description: TODO* @date 2021/9/12 19:07* @mark: show me the code , change the world*/ public class Artisan implements Serializable {private Integer age;private String name;public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;} }ObjectOutputStream#writeObject 實(shí)現(xiàn)序列化
把Artisan對(duì)象 (必須實(shí)現(xiàn)Serializable 接口)設(shè)置值后,寫(xiě)入一個(gè)文件,即序列化
import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream;/*** @author 小工匠* @version 1.0* @description: TODO* @date 2021/9/12 19:08* @mark: show me the code , change the world*/ public class Test {public static void main(String[] args) throws IOException {ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\artisan.out"));Artisan artisan = new Artisan();artisan.setAge(18);artisan.setName("artisan");objectOutputStream.writeObject(artisan);objectOutputStream.flush();objectOutputStream.close();} }ObjectInputStream#readObject方法實(shí)現(xiàn)反序列化
再把test.out文件讀取出來(lái),反序列化為Student對(duì)象
import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream;/*** @author 小工匠* @version 1.0* @description: TODO* @date 2021/9/12 19:09* @mark: show me the code , change the world*/ public class Test2 {public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\artisan.out"));Artisan art = (Artisan) objectInputStream.readObject();System.out.println("name="+art.getName());} }序列化底層實(shí)現(xiàn)分析
Serializable接口,只是一個(gè)空的接口,沒(méi)有方法或字段,為什么這么神奇,實(shí)現(xiàn)了它就可以讓對(duì)象序列化了?
為了驗(yàn)證Serializable的作用,寫(xiě)個(gè)ArtisanNoSerial對(duì)象,去掉實(shí)現(xiàn)Serializable接口,看序列化過(guò)程怎樣吧~
堆棧信息看一下
ObjectOutputStream 在序列化的時(shí)候,會(huì)判斷被序列化的Object是哪一種類型,String array enum 還是 Serializable,如果都不是的話,拋出 NotSerializableException異常。所以 Serializable真的只是一個(gè)標(biāo)志,一個(gè)序列化標(biāo)志 。
writeObject(Object)
序列化的方法就是writeObject, debug下
writeObject直接調(diào)用的就是writeObject0()方法
writeObject0 主要實(shí)現(xiàn)是對(duì)象的不同類型,調(diào)用不同的方法寫(xiě)入序列化數(shù)據(jù),這里面如果對(duì)象實(shí)現(xiàn)了Serializable接口,就調(diào)用writeOrdinaryObject()方法~
private void writeOrdinaryObject(Object obj,ObjectStreamClass desc,boolean unshared)throws IOException{......//調(diào)用ObjectStreamClass的寫(xiě)入方法writeClassDesc(desc, false);// 判斷是否實(shí)現(xiàn)了Externalizable接口if (desc.isExternalizable() && !desc.isProxy()) {writeExternalData((Externalizable) obj);} else {//寫(xiě)入序列化數(shù)據(jù)writeSerialData(obj, desc);}.....}writeSerialData()實(shí)現(xiàn)的就是寫(xiě)入被序列化對(duì)象的字段數(shù)據(jù)
private void writeSerialData(Object obj, ObjectStreamClass desc)throws IOException{for (int i = 0; i < slots.length; i++) {if (slotDesc.hasWriteObjectMethod()) {//如果被序列化的對(duì)象自定義實(shí)現(xiàn)了writeObject()方法,則執(zhí)行這個(gè)代碼塊slotDesc.invokeWriteObject(obj, this);} else {// 調(diào)用默認(rèn)的方法寫(xiě)入實(shí)例數(shù)據(jù)defaultWriteFields(obj, slotDesc);}}}defaultWriteFields()方法,獲取類的基本數(shù)據(jù)類型數(shù)據(jù),直接寫(xiě)入底層字節(jié)容器;獲取類的obj類型數(shù)據(jù),循環(huán)遞歸調(diào)用writeObject0()方法,寫(xiě)入數(shù)據(jù)~
private void defaultWriteFields(Object obj, ObjectStreamClass desc)throws IOException{ // 獲取類的基本數(shù)據(jù)類型數(shù)據(jù),保存到primVals字節(jié)數(shù)組desc.getPrimFieldValues(obj, primVals);//primVals的基本類型數(shù)據(jù)寫(xiě)到底層字節(jié)容器bout.write(primVals, 0, primDataSize, false);// 獲取對(duì)應(yīng)類的所有字段對(duì)象ObjectStreamField[] fields = desc.getFields(false);Object[] objVals = new Object[desc.getNumObjFields()];int numPrimFields = fields.length - objVals.length;// 獲取類的obj類型數(shù)據(jù),保存到objVals字節(jié)數(shù)組desc.getObjFieldValues(obj, objVals);//對(duì)所有Object類型的字段,循環(huán)for (int i = 0; i < objVals.length; i++) {......//遞歸調(diào)用writeObject0()方法,寫(xiě)入對(duì)應(yīng)的數(shù)據(jù)writeObject0(objVals[i],fields[numPrimFields + i].isUnshared());......}}FAQ
-
static靜態(tài)變量和transient 修飾的字段是不會(huì)被序列化的
-
serialVersionUID問(wèn)題
JAVA序列化的機(jī)制是通過(guò)判斷類的serialVersionUID來(lái)驗(yàn)證版本是否一致的。在進(jìn)行反序列化時(shí),JVM會(huì)把傳來(lái)的字節(jié)流中的serialVersionUID和本地相應(yīng)實(shí)體類的serialVersionUID進(jìn)行比較,如果相同,反序列化成功,如果不相同,就拋出InvalidClassException異常
如果確實(shí)需要修改某個(gè)類類,又想反序列化成功,怎么辦呢?可以手動(dòng)指定serialVersionUID的值,一般可以設(shè)置為1L或者,或者讓我們的編輯器IDE生成
-
如果某個(gè)序列化類的成員變量是對(duì)象類型,則該對(duì)象類型的類必須實(shí)現(xiàn)序列化
-
子類實(shí)現(xiàn)了序列化,父類沒(méi)有實(shí)現(xiàn)序列化,父類中的字段丟失問(wèn)題
總結(jié)
以上是生活随笔為你收集整理的每日一博 - Java序列化一二事儿的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 小工匠聊架构- 提升性能的大杀器之缓存技
- 下一篇: 每日一博 - tcpdump小技巧