transient用來表示一個域不是該對象序行化的一部分,當一個對象被序行化的時候,transient修飾的變量不會被序列化
ArrayList的動態數組elementData被transient 修飾的 那么豈不是反序列化后的ArrayList丟失了原先的元素, 其實不然. ArrayList在序列化的時候會調用writeObject,反序列化時調用readObject 也就是自定義序列化
- 為什么要自定義序列化?
- 因為ArrayList數組elementData中有未使用的空間 ,如果沒有使用的空間也序列化,勢必會影響性能.
- 基本概念
序列化:將一個對象轉換成一串二進制表示的字節數組,通過保存或轉移這些字節數據來達到持久化的目的。
反序列化:將字節數組重新構造成對象。
- 默認序列化
序列化只需要實現java.io.Serializable接口就可以了。序列化的時候有一個serialVersionUID參數,Java序列化機制是通過在運行時判斷類的serialVersionUID來驗證版本一致性的。 在進行反序列化,Java虛擬機會把傳過來的字節流中的serialVersionUID和本地相應實體類的serialVersionUID進行比較, 如果相同就認為是一致的實體類,可以進行反序列化,否則Java虛擬機會拒絕對這個實體類進行反序列化並拋出異常。
- serialVersionUID有兩 種生成方式:
1、默認的1L
2、根據類名、接口名、成員方法以及屬性等來生成一個64位的Hash字段
如果實現 java.io.Serializable接口的實體類沒有顯式定義一個名為serialVersionUID、類型為long的變量時,Java序列化 機制會根據編譯的.class文件自動生成一個serialVersionUID,如果.class文件沒有變化,那么就算編譯再多 次,serialVersionUID也不會變化。換言之,Java為用戶定義了默認的序列化、反序列化方法,其實就是ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法。
- 從以上對於序列化后的二進制文件的解析,我們可以得出以下幾個關鍵的結論:
1、序列化之后保存的是類的信息
2、被聲明為transient的屬性不會被序列化,這就是transient關鍵字的作用
3、被聲明為static的屬性不會被序列化,這個問題可以這么理解,序列化保存的是對象的狀態,但是static修飾的變量是屬於類的而不是屬於變量的,因此序列化的時候不會序列化它
- 手動指定序列化過程:
Java並不強求用戶非要使用默認的序列化方式,用戶也可以按照自己的喜好自己指定自己想要的序列化方式----只要你自己能保證序列化前后能得到想要的數據就好了。手動指定序列化方式的規則是:
進行序列化、反序列化時,虛擬機會首先試圖調用對象里的writeObject和readObject方法,進行用戶自定義的序列化和反序列化。如果沒有這 樣的方法,那么默認調用的是ObjectOutputStream的defaultWriteObject以及ObjectInputStream的 defaultReadObject方法。換言之,利用自定義的writeObject方法和readObject方法,用戶可以自己控制序列化和反序列 化的過程。這是非常有用的。
- 比如:
ArrayList的 elementData、HashMap的table
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
transient 當一個對象被序列化的時候,transient型變量的值不包括在序列化的表示中
顯然諸如 ArrayList在初始化的時候 就有空間了, 我們在操作list的時候 會存在未使用的空間,如果在序列化的時候把未使用的也序列化就不合理了
所以ArrayList有writeObject和readObject方法自定義了序列化與反序列化:
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(); } } 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(); } } }
- 序列化並不安全,因此有些場景下我們需要對一些敏感字段進行加密再序列化
- 復雜序列化情況總結
雖然Java的序列化能夠保證對象狀態的持久保存,但是遇到一些對象結構復雜的情況還是比較難處理的,最后對一些復雜的對象情況作一個總結:
1、當父類繼承Serializable接口時,所有子類都可以被序列化
2、子類實現了Serializable接口,父類沒有,父類中的屬性不能序列化(不報錯,數據丟失),但是在子類中屬性仍能正確序列化
3、如果序列化的屬性是對象,則這個對象也必須實現Serializable接口,否則會報錯
4、反序列化時,如果對象的屬性有修改或刪減,則修改的部分屬性會丟失,但不會報錯
5、反序列化時,如果serialVersionUID被修改,則反序列化時會失敗
