關於序列化的用法及基礎知識,因爲不是本文重點,這裏再也不詳細介紹了,這裏只作簡單的介紹。java
java.io.Serializable
接口,那麼它就能夠被序列化。ObjectOutputStream
和ObjectInputStream
對對象進行序列化及反序列化。private static final long serialVersionUID
)Serializable
接口。爲了深刻的介紹序列化,咱們這篇文章準備從Java源碼中的ArrayList類入手。看看Java自身是如何使用序列化的。在介紹ArrayList序列化以前,先來考慮一個問題:數組
問:如何自定義的序列化和反序列化策略?安全
帶着這個問題,咱們來看java.util.ArrayList
的源碼服務器
code1:dom
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 access private int size; }
筆者省略了其餘成員變量,從上面的代碼中能夠知道ArrayList實現了java.io.Serializable
接口,那麼咱們就能夠對它進行序列化及反序列化。由於負責保存元素的elementData是transient
的,因此咱們認爲這個成員變量的內容不會被序列化而保留下來。咱們寫一個Demo,驗證一下咱們的想法: 優化
code2:加密
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(new FileOutputStream("stringlist")); objectOutputStream.writeObject(stringList); IOUtils.close(objectOutputStream); File file = new File("stringlist"); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(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
其實就是用來保存列表中的元素的。經過該屬性的聲明方式,咱們認爲,他應該是沒法經過序列化持久化下來的。spa
問:爲何code 2的結果卻經過序列化和反序列化把List中的元素保留下來了呢?debug
在ArrayList中定義了來個方法: writeObject
和readObject
。這裏先給出結論:code
在序列化過程當中,若是被序列化的類中定義了writeObject 和 readObject 方法,虛擬機會試圖調用對象類裏的 writeObject 和 readObject 方法,進行用戶自定義的序列化和反序列化。若是沒有這樣的方法,則默認調用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
也就述說,用戶自定義的 writeObject 和 readObject 方法能夠容許用戶控制序列化的過程,好比能夠在序列化的過程當中動態改變序列化的數值。咱們發現ArrayList中有這兩個方法的實現,那麼基本能夠肯定,elementData能被序列化持久下來,確定和這兩個方法有關,雖然他被聲明爲transitent,那麼咱們來看一下ArrayList類中這兩個方法的具體實現:
code 3:
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff s.defaultReadObject(); // Read in capacity s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }
code4:
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int 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(); } }
經過上面兩段代碼,咱們發現,raedObject方法和writeObjec方法中定義了關於elementData的序列化策略。如今,咱們能夠回答剛剛的問題了。
問:爲何code 2的結果卻經過序列化和反序列化把List中的元素保留下來了呢?
答:ArrayList中定義了raedObject和writeObject方法,這兩個方法中定義了elementData的序列化及反序列化策略。
那麼,問題又來了。
問:爲何ArrayList要用這種方式來實現序列化呢?
why transient
ArrayList其實是動態數組,每次在放滿之後自動增加設定的長度值,若是數組自動增加長度設爲100,而實際只放了一個元素,那就會序列化99個null元素。爲了保證在序列化的時候不會將這麼多null同時進行序列化,ArrayList把元素數組設置爲transient。
why writeObject and readObject
前面說過,爲了防止一個包含大量空對象的數組被序列化,爲了優化存儲,因此,ArrayList使用transient
來聲明elementData
。 可是,做爲一個集合,在序列化過程當中還必須保證其中的元素能夠被持久化下來,因此,經過重寫writeObject
和 readObject
方法的方式把其中的元素保留下來。
writeObject
方法把elementData
數組中的元素遍歷的保存到輸出流(ObjectOutputStream)中。readObject
方法從輸入流(ObjectInputStream)中讀出對象並保存賦值到elementData
數組中。
至此,咱們先試着來回答剛剛提出的問題:
問:爲何ArrayList要用這種方式來實現序列化呢?
答:避免elementData數組中過多的無用的null被序列化。
問:如何自定義的序列化和反序列化策略?
答:能夠經過在被序列化的類中增長writeObject 和 readObject方法。
那麼問題又來了,雖然ArrayList中寫了writeObject 和 readObject 方法,可是這兩個方法並無顯示的被調用啊。
問:若是一個類中包含writeObject 和 readObject 方法,那麼這兩個方法是怎麼被調用的呢?
從code 4中,咱們能夠看出,對象的序列化過程經過ObjectOutputStream和ObjectInputputStream來實現的,那麼帶着剛剛的問題,咱們來分析一下ArrayList中的writeObject 和 readObject 方法究竟是如何被調用的呢?
爲了節省篇幅,這裏給出ObjectOutputStream的writeObject的調用棧:
writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject
這裏看一下invokeWriteObject:
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 suppressed throw new InternalError(ex); } } else { throw new UnsupportedOperationException(); } }
其中writeObjectMethod.invoke();
是關鍵,經過反射的方式調用writeObjectMethod方法。官方是這麼解釋這個writeObjectMethod的:
class-defined writeObject method, or null if none
在咱們的例子中,這個方法就是咱們在ArrayList中定義的writeObject方法。經過反射的方式被調用了。
至此,咱們先試着來回答剛剛提出的問題:
問:若是一個類中包含writeObject 和 readObject 方法,那麼這兩個方法是怎麼被調用的?
答:在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法時,會經過反射的方式調用。
至此,咱們已經介紹完了ArrayList的序列化方式。那麼,不知道有沒有人提出這樣的疑問:
問:Serializable明明就是一個空的接口,它是怎麼保證只有實現了該接口的方法才能進行序列化與反序列化的呢?
Serializable接口的定義:
public interface Serializable { }
若是嘗試對一個未實現Serializable接口的類進行序列化,會拋出java.io.NotSerializableException
。這是爲何呢?Serializable只是一個空接口,如何實現的呢?
其實這個問題也很好回答,咱們再回到剛剛ObjectOutputStream的writeObject的調用棧:
writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject
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
。
問:Serializable明明就是一個空的接口,它是怎麼保證只有實現了該接口的方法才能進行序列化與反序列化的呢?
答:在類的序列化過程當中,會使用instanceof關鍵字判斷一個類是否繼承了Serializable類,若是沒有,則直接拋出NotSerializableException異常。
一、若是一個類想被序列化,須要實現Serializable接口。不然將拋出NotSerializableException
異常,這是由於,在序列化操做過程當中會對類型進行檢查,要求被序列化的類必須屬於Enum、Array和Serializable類型其中的任何一種。
二、在變量聲明前加上該關鍵字,能夠阻止該變量被序列化到文件中。
三、在類中增長writeObject 和 readObject 方法能夠實現自定義序列化策略。