日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

序列化及反序列化的底層原理

今天我們深入分析一下JAVA序列化及反序列化的原理。

為了方便讀者理解,下面通過ArrayList的序列化來展開介紹Java是如何實(shí)現(xiàn)序列化及反序列化的。

在介紹ArrayList序列化之前,先考慮一個(gè)問題:

如何自定義序列化和反序列化的策略?

帶著這個(gè)問題,我們看一下java.util.ArrayList的源碼:

public class ArrayList<E> extends AbstractList<E>implements List<E>, Randomaccess, Cloneable, java.io.Serializable{private static final long serialVersionUID = 8683452581122892189L;transient Object[] elementData; // non-private to simplify nested class accessprivate int size;}

上面的代碼中忽略了其他成員變量,ArrayList實(shí)現(xiàn)了java.io.Serializable接口,我們對(duì)它進(jìn)行序列化及反序列化。

我們看到,ArrayList中的elementData被定義為transient類型,而被定義為transient類型的成員變量不會(huì)被序列化而保留下來。

我們寫一個(gè)Demo,驗(yàn)證一下我們的想法:

public static void main(String[] args) throws IOException, ClassNotFoundException {List<String> stringList = new ArrayList<String>();stringList.add("hello");stringList.add("world");stringList.add("hollis");stringList.add("chuang");System.out.println("init StringList" + stringList);ObjectOutputStream objectOutputStream = new ObjectOutputStream(newFileOutputStream("stringlist"));objectOutputStream.writeObject(stringList);IOUtils.close(objectOutputStream);File file = new File("stringlist");ObjectInputStream objectInputStream = new ObjectInputStream(newFileInputStream(file));List<String> newStringList = (List<String>)objectInputStream.readObject();IOUtils.close(objectInputStream);if(file.exists()){file.delete();}System.out.println("new StringList" + newStringList);}// init StringList[hello, world, hollis, chuang]// new StringList[hello, world, hollis, chuang]

了解ArrayList的讀者都知道,ArrayList底層是通過數(shù)組實(shí)現(xiàn)的。那么數(shù)組elementData其實(shí)就是用來保存列表中的元素的。通過該屬性的聲明方式我們知道,它是無法通過序列化持久化下來的。

那么為什么上面代碼的結(jié)果卻通過序列化和反序列化把List中的元素保留下來了呢?

1. writeObject 和readObject 方法

在ArrayList中定義了兩個(gè)方法:writeObject和readObject。

這里先給出結(jié)論:

在序列化過程中,如果被序列化的類中定義了writeObject和readObject方法,那么虛擬機(jī)會(huì)試圖調(diào)用對(duì)象類中的writeObject和readObject方法進(jìn)行用戶自定義的序列化和反序列化操作。

如果沒有這樣的方法,則默認(rèn)調(diào)用的是ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法。

用戶自定義的writeObject和readObject方法允許用戶控制序列化的過程,比如可以在序列化的過程中動(dòng)態(tài)改變序列化的數(shù)值。

下面看一下這兩個(gè)方法的具體實(shí)現(xiàn):

private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {elementData = EMPTY_ELEMENTDATA;// Read in size, and any hidden stuffs.defaultReadObject();// Read in capacitys.readInt(); // ignoredif (size > 0) {// be like clone(), allocate array based upon size not capacityensureCapacityInternal(size);Object[] a = elementData;// Read in all elements in the proper order.for (int i=0; i<size; i++) {a[i] = s.readObject();}}}private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException{// Write out element count, and any hidden stuffint expectedModCount = modCount;s.defaultWriteObject();// Write out size as capacity for behavioural compatibility with clone()s.writeInt(size);// Write out all elements in the proper order.for (int i=0; i<size; i++) {s.writeObject(elementData[i]);}if (modCount != expectedModCount) {throw new ConcurrentModificationException();}}

為什么ArrayList要用這種方式來實(shí)現(xiàn)序列化呢?

2. 為什么使用transient

ArrayList實(shí)際上是動(dòng)態(tài)數(shù)組,每次在放滿以后自動(dòng)增長設(shè)定的長度值,如果數(shù)組自動(dòng)增長的長度設(shè)為100,而實(shí)際只放了1個(gè)元素,那么就會(huì)序列化99個(gè)null元素。為了保證不會(huì)對(duì)這么多null元素同時(shí)進(jìn)行序列化,ArrayList把元素?cái)?shù)組設(shè)置為transient。

3. 為什么重寫writeObject 和readObject

前面說過,為了防止一個(gè)包含大量空對(duì)象的數(shù)組被序列化,以及優(yōu)化存儲(chǔ),ArrayList使用transient來聲明elementData。

但是,作為一個(gè)集合,在序列化過程中還必須保證其中的元素可以被持久化下來,所以,通過重寫writeObject和readObject方法的方式把其中的元素保留下來。

● writeObject方法把elementData數(shù)組中的元素遍歷地保存到輸出流(ObjectOutputStream)中。

● readObject方法從輸入流(ObjectInputStream)中讀出對(duì)象并保存賦值到 elementData數(shù)組中。

至此,我們回答剛才提出的問題:

如何自定義序列化和反序列化的策略?
答:可以在被序列化的類中增加writeObject和readObject方法。

問題又來了:

雖然ArrayList中寫了writeObject和readObject方法,但是這兩個(gè)方法并沒有顯式地被調(diào)用。
如果一個(gè)類中包含writeObject和readObject 方法,那么這兩個(gè)方法是怎么被調(diào)用的呢?

4.ObjectOutputStream

對(duì)象的序列化過程是通過ObjectOutputStream和ObjectInputStream實(shí)現(xiàn)的,帶著剛才的問題,我們分析一下ArrayList中的writeObject和readObject方法到底是如何被調(diào)用的。

為了節(jié)省篇幅,這里給出ObjectOutputStream的writeObject的調(diào)用棧:

writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObjectinvokeWriteObject 如下:void invokeWriteObject(Object obj, ObjectOutputStream out)throws IOException, UnsupportedOperationException{if (writeObjectMethod != null) {try {writeObjectMethod.invoke(obj, new Object[]{ out });} catch (InvocationTargetException ex) {Throwable th = ex.getTargetException();if (th instanceof IOException) {throw (IOException) th;} else {throwMiscException(th);}} catch (IllegalAccessException ex) {// should not occur,as access checks have been suppressedthrow new InternalError(ex);}} else {throw new UnsupportedOperationException();}}

其中writeObjectMethod.invoke(obj, new Object[]{ out })是關(guān)鍵,通過反射的方式調(diào)用writeObjectMethod方法。官方是這么解釋這個(gè)writeObjectMethod的:

class-defined writeObject method, or null if none

在我們的例子中,這個(gè)方法就是在ArrayList中定義的writeObject方法,通過反射的方式被調(diào)用了。

至此,我們回答剛才提出的問題:

如果一個(gè)類中包含writeObject和readObject方法,那么這兩個(gè)方法是怎么被調(diào)用的呢?
答:在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法時(shí),會(huì)通過反射的方式調(diào)用。

有的讀者可能會(huì)提出這樣的疑問:

Serializable明明就是一個(gè)空的接口,它是怎么保證只有實(shí)現(xiàn)了該接口的方法才能進(jìn)行序列化與反序列化的呢?

Serializable接口的定義如下:

public interface Serializable {}

當(dāng)嘗試對(duì)一個(gè)未實(shí)現(xiàn)Serializable或者Externalizable接口的對(duì)象進(jìn)行序列化時(shí),會(huì)拋出
java.io.NotSerializableException異常。

其實(shí)這個(gè)問題也很好回答,我們?cè)倩氐絼偛臤bjectOutputStream的writeObject的調(diào)用棧:

writeObject0方法中有如下一段代碼:

if (obj instanceof String) {writeString((String) obj, unshared);} else if (cl.isArray()) {writeArray(obj, desc, unshared);} else if (obj instanceof Enum) {writeEnum((Enum<?>) obj, desc, unshared);} else if (obj instanceof Serializable) {writeOrdinaryObject(obj, desc, unshared);} else {if (extendedDebugInfo) {throw new NotSerializableException(cl.getName() + "n" + debugInfoStack.toString());} else {throw new NotSerializableException(cl.getName());}}

在進(jìn)行序列化操作時(shí),會(huì)判斷要被序列化的類是否是Enum、Array和Serializable類型,如果不是則直接拋出NotSerializableException異常。

小結(jié)

(1)如果一個(gè)類想被序列化,則需要實(shí)現(xiàn)Serializable接口,否則將拋出NotSerializable-Exception異常,這是因?yàn)樵谛蛄谢僮鬟^程中會(huì)對(duì)類的類型進(jìn)行檢查,要求被序列化的類必須屬于Enum、Array和Serializable類型中的任何一種。

(2)在變量聲明前加上關(guān)鍵字transient,可以阻止該變量被序列化到文件中。

(3)在類中增加writeObject和readObject方法可以實(shí)現(xiàn)自定義的序列化策略。

 

內(nèi)容摘自《深入理解Java核心技術(shù)》,作者是Hollis,張洪亮,阿里巴巴技術(shù)專家,51CTO 專欄作家,CSDN 博客專家,掘金優(yōu)秀作者,《程序員的三門課》聯(lián)合作者,《Java工程師成神之路》系列文章作者;熱衷于分享計(jì)算機(jī)編程相關(guān)技術(shù),博文全網(wǎng)閱讀量數(shù)千萬。

分享到:
標(biāo)簽:Java
用戶無頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績?cè)u(píng)定