天天看點

JAVA序列化

什麼是序列化和反序列化

    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的值

序列化前和序列化後的對象的關系

    序列化時深複制,反序列化還原後的對象位址與原來的不同。

    如下例子:

    序列化前後對象的位址不同了,但是内容是一樣的,而且對象中包含的引用也相同。換句話說,通過序列化操作,我們可以實作對任何可Serializable對象的”深度複制(deep copy)"——這意味着我們複制的是整個對象網,而不僅僅是基本對象及其引用。對于同一流的對象,他們的位址是相同,說明他們是同一個對象,但是與其他流的對象位址卻不相同。也就說,隻要将對象序列化到單一流中,就可以恢複出與我們寫出時一樣的對象網,而且隻要在同一流中,對象都是同一個。

破壞單例模式

    序列化和反序列化可能會破壞單例。上面的例子就是個很好的證明,為了更形象,在舉一個單例模式的序列化的例子,有關單例模式不了解的話,可以參考博文http://blog.csdn.net/u013256816/article/details/50427061

    說明測試代碼中的s和s1指向了不同的執行個體,在反序列化後,生成多個對象執行個體。

    接下來我們稍微修改一下SerSingleton類,如下所示:

    注意程式中加粗的部分,然後運作測試代碼,結果如下:

    至于為什麼,可以檢視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後的變量不能被序列化