序列化Serializable和Parcelable
概念
Java中的序列化是一種將物件持久化(比如儲存在磁碟)的手段。一般情況下,程式執行(即JVM執行)時,Java物件(短暫)儲存在記憶體中。但JVM停止執行後,物件的狀態資訊就不能儲存在記憶體了。我們需要將物件持久化儲存,這就是序列化的用途。
簡單說,序列化就是,將物件的狀態資訊轉換為位元組序列的過程。通過序列化,可以將物件的狀態資訊轉換為位元組序列,然後通過IO流儲存到磁碟或者網路傳輸。然後從磁碟或網路請求獲取到位元組流,通過反序列化,重新建立該物件。
Serializable
Serializable是Java提供的序列化介面,實現Serializable介面的類,極為可序列化的類。可以直接通過ObjectOutputStream序列化,也可以通過ObjectInputStream反序列化。
以下便是Java序列化的典型用法
public class SerializableTest implements Serializable { private final static long serialVersionUID = 1L; private String name; private String job; private UnSerializable unSerializable; public SerializableTest(String name, String job,UnSerializable unSerializable) { this.name = name; this.job = job; this.unSerializable = unSerializable; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public UnSerializable getUnSerializable() { return unSerializable; } public void setUnSerializable(UnSerializable unSerializable) { this.unSerializable = unSerializable; } }
serialVersionUID
serialVersionUID是類的序列化ID,可以理解為這個類的版本號。serialVersionUID可以自己手動宣告,也可以由系統預設。系統預設的serialVersionUID是根據類的定義計算出一個serialVersionUID。如果類的定義發生了改變,預設的serialVersionUID也會隨之變化,就像類的版本發生了改變一樣。
serialVersionUID的作用在於,虛擬機器是否允許反序列化,不僅僅是取決於類的路徑和功能程式碼是否一致。還有一個非常重要的判斷依據是,兩個類的serialVersionUID是否一致。
具體的場景就是:客戶端A和B通過網路傳遞物件資料,客戶端A將c物件序列化為位元組流,通過網路傳輸給客戶端B,然後B反序列化得到c。此時如果A和B端的app版本不同,而導致C類的serialVersionUID不同,就會導致B端反序列化失敗。
當然,有時候,我們也可以通過這種方法,強制app的低版本升級
Java官方是建議我們手動宣告serialVersionUID。一般情況下沒有特殊需求,serialVersionUID就直接宣告為1L。
成員變數序列化
一個物件的成員變數是不可序列化的物件的引用,會導致物件序列化失敗,丟擲NotSerializableException異常。序列化要求,物件的成員變數也是可序列化的。
靜態變數序列化
序列化儲存的是物件的狀態資訊,靜態變數屬於類的狀態,因此,序列化不儲存靜態變數。
父類的序列化
一個子類實現了Serializable介面,它的父類沒有實現Serializable介面。序列化該子類物件,然後反序列化輸出其父類定義的變數的值,該變數的值和序列化之前不同。
如果父類沒有實現Serializable介面,虛擬機器是不會序列化父類物件的。此時就要求,父類必須有無參建構函式。而一個Java物件的構造必須先有父類物件,再建立子類物件,反序列化時也不例外。所以反序列化時,虛擬機器只能呼叫父類的無參建構函式來建立父類物件。這樣,父類定義的成員變數的值,就是呼叫無參建構函式後的值。如果你考慮到了這種序列化的情況,就可以在父類的無參建構函式對成員變數賦值。
Transient
Transient關鍵字的作用就是控制變數的序列化,在變數宣告前宣告Transient,表示該變數不會被序列化。在反序列化時,該變數值就會是該型別的初始值,如int型為0,String型為null。
自定義readObject和WriteObject
將物件序列化後,進行網路傳輸,就要考慮到資料的安全性問題。這樣的場景,可以考慮封裝自定義的writeObject和readObject方法。這樣可以序列化時,對資料進行加密,反序列化時解密。
public class SerializableTest implements Serializable { private final static long serialVersionUID = 1L; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } private void writeObject(ObjectOutputStream out) { try { ObjectOutputStream.PutField putFields = out.putFields(); System.out.println("原name:" + name); name = "encryption-"+name;//模擬加密 putFields.put("name", name); System.out.println("加密後的name" + name); out.writeFields(); } catch (IOException e) { e.printStackTrace(); } } private void readObject(ObjectInputStream in) { try { ObjectInputStream.GetField readFields = in.readFields(); Object object = readFields.get("name", ""); System.out.println("要解密的字串:" + object.toString()); name = object.toString().split("-")[1];//模擬解密 } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
序列化儲存規則
Java序列化機制為了節省磁碟空間,具有特定的儲存規則,當多次序列化寫入檔案的是同一物件時,並不會再將物件的內容寫入檔案,而只是再儲存一個引用,此時寫入檔案的就是新增的引用和一些控制資訊。反序列化時,恢復引用關係,使得所有的引用指向的是同一個物件。這樣的特點,會帶來另一個值得注意的問題:
File file = new File("H:/sourceCode/workspace4java/test.txt"); SerializableTest testObject = new SerializableTest("Smith","engineer",32,new UnSerializable("1024")); FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(testObject); oos.flush(); testObject.setName("James"); oos.writeObject(testObject); oos.close(); FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis); SerializableTest result1 = (SerializableTest)ois.readObject(); SerializableTest result2 = (SerializableTest)ois.readObject(); System.out.println(result1.getName()); System.out.println(result2.getName());
輸出結果是
SmithSmith
我們希望看到的是第二次反序列化的物件name為James,但是可以看到,兩次反序列化的物件的name一樣。這是因為第二次序列化寫入時,虛擬機器根據引用關係,已經判斷出寫入的是同一物件,因此只儲存了第二次的引用。所以反序列化讀取時,都是第一次儲存的物件。
Parcelable
Parcelable是Android提供的序列化介面。它相比於Serializable的優點是,序列化的效率要高一些。 實現了 Parcelable 介面的類在序列化和反序列化時會被轉換為 Parcel 型別的資料 。 Parcel 是一個載體,它可以包含資料或者物件引用,然後通過 IBinder 在程序間傳遞。
Parcelable和Serializable的對比:
- 一般在儲存資料到 SD 卡或者網路傳輸時建議使用 Serializable 即可,雖然效率差一些,好在使用方便。
- 而在執行時資料傳遞時建議使用 Parcelable,比如 Intent,Bundle 等,Android 底層做了優化處理,效率很高。