Java的序列化流程如下:
Java的反序列化流程如下:
注意:並不是所有類都需要進行序列化,主要原因有兩個
1)安全問題。 Java中有的類別屬於敏感類,此類的物件資料不便對外公開,而序列化的物件資料很容易進行破解,無法保證其資料的安全性,因此一般這種類型的物件不會進行序列化。
2)資源問題。可以使用序列化位元組流建立對象,而且這種創建時不受限制的,有時過多地創建對象會造成很大的資源問題,因此此類對像也不適宜進行序列化。
Serializable
Serializable是Java提供的一個序列化接口,它是一個空接口,專門為對象提供標準的序列化跟反序列化操作。
序列化過程:
Person p = new Person("name","id"); File file = new File("cache.txt"); FileOutputStream output = new FileOutputStream(file); ObjectOutputStream objectOutputStream = new ObjectOutputStream(output); objectOutputStream.writeObject(p); output.close(); objectOutputStream.close();
反序列化過程:
File file = new File("cache.txt"); FileInputStream input= new FileInputStream(file); ObjectInputStream objectInputStream = new ObjectInputStream(input); Person p = (Person)objectInputStream.readObject(); System.out.println(p.getName()+"---"+p.getId()); input.close(); objectInputStream.close();
需要序列化的類成員
對象時並不是所有成員都要轉換成二進位的字節序列,為了節省的字節序列傳輸空間以及提高序列化效率,有些不必要的成員是無需序列化的。其中包括:
靜態變數。因為靜態變數屬於類別的屬性,並不屬於某個具體實例,因此在序列化的時候無須進行序列化,反序列化時,可以直接取得類別的靜態成員引用。
方法。方法只是一系列的操作集合,方法不會依賴對象,不會因為對象的不同,而操作不同,反序列化時,也可以從類別直接取得方法資訊。
繼承關係的序列化
父類別實作Serializable時,子類別被序列化,父類別也會被序列化。
父類沒有實現Serializable時,子類被序列化,父類不會被序列化
引用關係的序列化
如果對一個實現了Serializable的類進行序列化操作,則對它對一個實現了Serializable的類進行序列化操作的引用類別進行序列化操作。如果引用類別沒有實作Serializable接口,JVM會拋出java.io.NotSerializableExeception.
class Person implements Serializable{ private String name; private Tool tool = new Tool(); } class Tool implements Serializable{ }
此時對Person類別進行序列化操作,則會同時對Tool類別進行序列化操作。若Tool類別沒有實作Serializable接口,則會拋出異常。
保護敏感資料:
一個類別加上序列化識別後,該類別物件的所有屬性資訊將被序列化,然後進行本地儲存或網路傳輸。然後有時物件中的某些字段屬於敏感訊息,不應暴露出來。如果也進行序列化,容易被破解,從而 造成安全隱患,例如常見的密碼欄位。
Java提供一個關鍵字transient,即瞬時關鍵字。這個關鍵字關閉欄位的序列化,這樣受保護的資訊就不會因為序列化而對外暴露。
序列化標識ID
試想一下這樣的情景:兩端進行網絡傳輸序列化對象,由於某種原因,導致兩端使用的類的版本不同,假設接收方的類被刪除了幾個字段。當發送發送將物件的序列化位元組流傳送到接收方時,由於接收方 的類別少了幾個字段,而無法解析。
Java要求實現序列化介面的類別都必須宣告一個serialVersionUID靜態屬性,如果沒有該屬性JVM也會自動宣告該屬性,並為該屬性賦值(當類別發生改變時會賦予不同的值)。此屬性的值是唯一的,用於 識別不同的序列化類別。只有類別的序列化標識完全相同,Java才會進行反序列化工作,這就是序列化標識的功能。
對於前面提到的情景,假設沒有手動聲明serialVersionUID,則JVM對發送方跟接收方使用的類別中的serialVersionUID賦予不同的值,則反序列化失敗。當手動將serialVersionUID賦值時,即使類別的字 段改變,也能夠反序列化成功。
自訂序列化策略
定制序列化策略
Java提供了一套有效的機制,允許在序列化和反化序列化處理時,使用的序列進行定制。當傳輸雙方協定好序列化策略後,只需要在需要傳輸的序列化類別中加入一組方法來實現這組策略,在序列化時會自動呼叫這些規定好的方法進行序列化和反序列化。方法如下:
1)private void writeObject(ObjectOutputSteam out) throws IOException
在方法的内部有重要的代码:out.defaultWriteObject() //将对象数据以默认方式写入到输出流中
2)private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException
同样的,此方法内部也有相似代码:in.defaultReadObject(); //以默认方式从输入流中恢复对象
这两个方法的作用分别是将特定的对象写入到输出流中以及从输入流中恢复特定的对象,通过这两个方法,用户即可实现自定义的序列化。当在实现Serializable接口的类中写了上面两个方法之后,序列化或反序列化该类时则会通过反射来调用这两个方法,从而实现自定义序列化。
限制序列化对象的数量
我们看下面的单例模式:
public class Singleton implements Serializable { private volatile static Singleton mInstance; private Singleton() { } public static Singleton getInstance() { if (mInstance == null) { synchronized (Singleton.class) { if (mInstance == null) { mInstance = new Singleton(); } } } return mInstance; } }
此时通过反序列化获取实例,则单例模式会失效。那该如何解决这个问题呢?
Java有一种机制,可以让我们在序列化和反序列化时,可以根据自己的需要,写入或读取指定的实例。使用这种机制,需要在实现Serializable接口的类中添加两个方法:
private Object readResolve() //如果用户在序列化类中添加了该方法,则在进行反序列化时,使用该方法返回的对象,作为反序列化对象。
private Object writeReplace() //如果用户在序列化类中添加了该方法,则在进行序列化时,序列化该类返回的对象。
再看使用了该机制的单例模式:
public class Singleton implements Serializable { private volatile static Singleton mInstance; private Singleton() { } public static Singleton getInstance() { if (mInstance == null) { synchronized (Singleton.class) { if (mInstance == null) { mInstance = new Singleton(); } } } return mInstance; } private Object readResolve() { return getInstance(); } private Object writeReplace() { return getInstance(); } }
此时的通过反序列化得到的对象也是同一个,即单例模式依然有效!
相关文章:
Java序列化Serializable和Externalizable区别的示例代码