什麼是序列化和反序列化
Serialization(序列化)是一種将對象以一連串的位元組描述的過程;反序列化deserialization是一種将這些位元組重建成一個對象的過程。
什麼情況下需要序列化
當你想把的記憶體中的對象儲存到一個檔案中或者資料庫中時候(資料持久化);
利用序列化實作遠端通信,即在網絡上傳送對象的位元組序列;
如何實作序列化
将需要序列化的類實作Serializable接口就可以了,Serializable接口中沒有任何方法,可以了解為一個标記,即表明這個類可以序列化.
序列化和反序列化例子
如果我們想要序列化一個對象,首先要建立某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然後将這些OutputStream封裝在一個ObjectOutputStream中。這時候,隻需要調用writeObject()方法就可以将對象序列化,并将其發送給OutputStream(記住:對象的序列化是基于位元組的,不能使用Reader和Writer等基于字元的層次結構)。而反序列的過程(即将一個序列還原成為一個對象),需要将一個InputStream(如FileInputstream、ByteArrayInputStream等)封裝在ObjectInputStream内,然後調用readObject()即可。
序列化的資料含有那些資訊
這裡舉個例子将上面例子中的serialize.obj的資訊讀取出來:
運作結果:
解析:
第一部分是序列化檔案頭
AC ED:STREAM_MAGIC聲明使用了序列化協定
00 05:STREAM_VERSION序列化協定版本
73:TC_OBJECT聲明這是一個新的對象
第二部分是序列化類的描述
72:TC_CLASSDESC聲明這裡開始一個新class
00 17:class名字的長度是23位元組
63 6F 6D 2E 73 65 72 69 61 6C 69 7A 65 2E 53 65 72 69 61 6C 69 7A 65:類名(ASCII碼:com.serialize.Serialize)
B7 AD 6C AC 04 0E D0 8C: SerialVersionUID
02:标記号,改值聲明改對象支援序列化
00 01:該類所包含的域的個數為1
第三部分是對象中各個屬性項的描述
49:域類型,代表I,表示Int類型(又如:44,查ASCII碼表為D,代表Double類型)
00 03:域名字的長度,為3
6E 75 6D: num屬性的名稱
第四部分輸出該對象父類資訊描述,這裡沒有父類,如果有,則資料格式與第二部分一樣
78:TC_ENDBLOCKDATA,對象塊接收标志
70:TC_NULL,說明沒有其他超類的标志
第五部分輸出對象的屬性的實際值,如果屬性項是一個對象,那麼這裡還将序列化這個對象,規則和第2部分一樣。
00 00 05 6E:1390的值
序列化前和序列化後的對象的關系
序列化時深複制,反序列化還原後的對象位址與原來的不同。
破壞單例模式
序列化和反序列化可能會破壞單例。序列化前和序列化後的對象的關系已說明了這點。
如何防止單例被破壞?在單例的類中加readResolve方法。當ObjectIputStream調用的時候可以不産出新的對象。
檢視ObjectIputStream.class的源碼,當中有這樣一段話:
Deserializing an object via readUnshared invalidates the stream handle associated with the returned object. Note that this in itself does not always guarantee that the reference returned by readUnshared is unique; the deserialized object may define a readResolve method which returns an object visible to other parties, or readUnshared may return a Class object or enum constant obtainable elsewhere in the stream or through external means. If the deserialized object defines a readResolve method and the invocation of that method returns an array, then readUnshared returns a shallow clone of that array; this guarantees that the returned
array object is unique and cannot be obtained a second time from an invocation of readObject or readUnshared on the ObjectInputStream, even if the underlying data stream has been manipulated.
序列化ID
序列化 ID 在 Eclipse 下提供了兩種生成政策,一個是固定的 1L,一個是随機生成一個不重複的 long 類型資料(實際上是使用 JDK 工具生成),在這裡有一個建議,如果沒有特殊需求,就是用預設的 1L 就可以,這樣可以確定代碼一緻時反序列化成功。這也可能是造成序列化和反序列化失敗的原因,因為不同的序列化id之間不能進行序列化和反序列化。
靜态變量能否序列化
序列化會忽略靜态變量,即序列化不儲存靜态變量的狀态。靜态成員屬于類級别的,是以不能序列化。即序列化的是對象的狀态不是類的狀态。這裡的不能序列化的意思,是序列化資訊中不包含這個靜态成員域。transient後的變量也不能序列化。
transient使用小結
一旦變量被transient修飾,變量将不再是對象持久化的一部分,該變量内容在序列化後無法獲得通路。
transient關鍵字隻能修飾變量,而不能修飾方法和類。注意,本地變量是不能被transient關鍵字修飾的。變量如果是使用者自定義類變量,則該類需要實作Serializable接口。
被transient關鍵字修飾的變量不再能被序列化,一個靜态變量不管是否被transient修飾,均不能被序列化。
總結
當父類繼承Serializable接口時,所有子類都可以被序列化。
子類實作了Serializable接口,父類沒有,父類中的屬性不能被序列化(不報錯,資料不會丢失),但是在子類中的屬性仍能正确序列化
如果序列化的屬性是對象,則這個對象也必須實作Serializable接口,否則會報錯。
在反序列化時,如果對象的屬性有修改或删減,則修改的部分屬性會丢失,但不會報錯。
在反序列化時,如果serialVersionUID被修改,則反序列化時會失敗
當一個對象的執行個體變量引用其他對象,序列化該對象時,也把引用對象進行序列化
static,transient後的變量不能被序列化
建議
在java環境下,java序列化能夠很好的工作,但在多語言環境下,用java序列化存儲後,很難用其他語言來還原出結果,在這種情況下,還是要盡量存儲通用資料結構,如json或者xml的結構資料,目前也有比較好的序列化工具,如google的protobuf。