亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.430618.com 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

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

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

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

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

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

帶著這個問題,我們看一下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實現了java.io.Serializable接口,我們對它進行序列化及反序列化。

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

我們寫一個Demo,驗證一下我們的想法:

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底層是通過數組實現的。那么數組elementData其實就是用來保存列表中的元素的。通過該屬性的聲明方式我們知道,它是無法通過序列化持久化下來的。

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

1. writeObject 和readObject 方法

在ArrayList中定義了兩個方法:writeObject和readObject。

這里先給出結論:

在序列化過程中,如果被序列化的類中定義了writeObject和readObject方法,那么虛擬機會試圖調用對象類中的writeObject和readObject方法進行用戶自定義的序列化和反序列化操作。

如果沒有這樣的方法,則默認調用的是ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法。

用戶自定義的writeObject和readObject方法允許用戶控制序列化的過程,比如可以在序列化的過程中動態改變序列化的數值。

下面看一下這兩個方法的具體實現:

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要用這種方式來實現序列化呢?

2. 為什么使用transient

ArrayList實際上是動態數組,每次在放滿以后自動增長設定的長度值,如果數組自動增長的長度設為100,而實際只放了1個元素,那么就會序列化99個null元素。為了保證不會對這么多null元素同時進行序列化,ArrayList把元素數組設置為transient。

3. 為什么重寫writeObject 和readObject

前面說過,為了防止一個包含大量空對象的數組被序列化,以及優化存儲,ArrayList使用transient來聲明elementData。

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

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

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

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

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

問題又來了:

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

4.ObjectOutputStream

對象的序列化過程是通過ObjectOutputStream和ObjectInputStream實現的,帶著剛才的問題,我們分析一下ArrayList中的writeObject和readObject方法到底是如何被調用的。

為了節省篇幅,這里給出ObjectOutputStream的writeObject的調用棧:

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 })是關鍵,通過反射的方式調用writeObjectMethod方法。官方是這么解釋這個writeObjectMethod的:

class-defined writeObject method, or null if none

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

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

如果一個類中包含writeObject和readObject方法,那么這兩個方法是怎么被調用的呢?
答:在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法時,會通過反射的方式調用。

有的讀者可能會提出這樣的疑問:

Serializable明明就是一個空的接口,它是怎么保證只有實現了該接口的方法才能進行序列化與反序列化的呢?

Serializable接口的定義如下:

public interface Serializable {}

當嘗試對一個未實現Serializable或者Externalizable接口的對象進行序列化時,會拋出
java.io.NotSerializableException異常。

其實這個問題也很好回答,我們再回到剛才ObjectOutputStream的writeObject的調用棧:

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());}}

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

小結

(1)如果一個類想被序列化,則需要實現Serializable接口,否則將拋出NotSerializable-Exception異常,這是因為在序列化操作過程中會對類的類型進行檢查,要求被序列化的類必須屬于Enum、Array和Serializable類型中的任何一種。

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

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

 

內容摘自《深入理解Java核心技術》,作者是Hollis,張洪亮,阿里巴巴技術專家,51CTO 專欄作家,CSDN 博客專家,掘金優秀作者,《程序員的三門課》聯合作者,《Java工程師成神之路》系列文章作者;熱衷于分享計算機編程相關技術,博文全網閱讀量數千萬。

分享到:
標簽:Java
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定