1、概念
(1)序列化
將數據結構或者對象轉換成二進制序列的過程
(2)反序列化
將序列化過程所生成的二進制序列轉換成數據結構或者對象的過程
(3)持久化
將數據結構或者對象存儲起來,如內存、磁盤。
2、序列化的作用
進程之間、客戶端和服務器之間數據的傳輸。因爲傳輸過程只能傳輸二進制序列。
例如如下圖:
在客戶端和服務器之間網絡數據傳輸的時候,我們可以選擇Serializable序列化或者廣義的json,xml,protbuf ,在安卓的進程之間通信的時候,可以選擇Parcelable 序列化。
3、序列化的類型
(1)Serializable
Java獨有的序列化
(2) Parcelable
Android獨有的序列
(3)廣義的序列化
json,xml,protbuf .
4、Serializable序列化方式
對象通過實現Serializable接口或者Externalizable接口,Externalizable接口也是實現了Serializable接口
關於Serializable的幾個問題
(1)serialVersionUID
serialVersionUID通常是對象的哈希碼,主要用於對象的版本控制唯一標識。
序列化和反序列化的時候類中的serialVersionUID一定要一致。如果序列化時的serialVersionUID和反序列化時的serialVersionUID不一樣,將會拋出異常。舉個例子
我們定義了Student 類中的serialVersionUID = 1,然後將其序列化到磁盤在反序列化出來的時候,不修改serialVersionUID
package com.it.test.seriv;
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = 1;
private String name;
private String sex;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
public Student(String name, String sex) {
this.name = name;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public static void main(String[] args) {
//序列化
Student student = new Student("小明","男");
System.out.println("寫入....");
SerializeableUtils.saveObject(student,"D:\\xuliehuaTest\\");
//反序列化
Student student1 = SerializeableUtils.readObject("D:\\xuliehuaTest\\");
System.out.println("讀出....");
System.out.println(student1.toString());
}
}
序列化和反序列化都成功
寫入....
讀出....
Student{name='小明', sex='男'}
我們將serialVersionUID 改成2,再反序列化出來 試試
private static final long serialVersionUID = 2;
public static void main(String[] args) {
//序列化
// Student student = new Student("小明","男");
// System.out.println("寫入....");
// SerializeableUtils.saveObject(student,"D:\\xuliehuaTest\\");
//反序列化
Student student1 = SerializeableUtils.readObject("D:\\xuliehuaTest\\");
System.out.println("讀出....");
System.out.println(student1.toString());
}
提示serialVersionUID 不一致的異常。
java.io.InvalidClassException: com.it.test.seriv.Student; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at com.it.test.seriv.SerializeableUtils.readObject(SerializeableUtils.java:71)
at com.it.test.seriv.Student.main(Student.java:42)
讀出....
Exception in thread "main" java.lang.NullPointerException
at com.it.test.seriv.Student.main(Student.java:44)
因此我們知道serialVersionUID控制了對象的版本,序列化和反序列化的時候serialVersionUID一定要一致。
當然我們也可以不寫serialVersionUID,這樣JVM會自動給我們添加上serialVersionUID的值。當類沒有任何改變的時候,我們直接序列化和反序列化是正常的,但是假如我們在序列化之後,刪除或者添加了類的某個成員,JVM就會修改serialVersionUID的值,就會導致和序列化時候的serialVersionUID不一致。這樣我們在反序列化的時候就還會報serialVersionUID不一致的錯誤。因此當我們需要序列化對象的時候,一般手動設置serialVersionUID的值。
(2)希望某個成員不被序列化
使用 transient 關鍵字修飾成員
private transient String name;
(3)如果類中的一個成員沒實現序列化接口,我們去序列化這個類的對象會怎麼樣?
如果序列化的對象沒有包含這個成員,則正常,如果包含,則會報拋異常。
舉例
Student中包含2個構造方法,其中User成員沒有實現Serializable接口
private String name;
private String sex;
private User user;
public Student(String name, String sex) {
this.name = name;
this.sex = sex;
this.user = user;
}
public Student(String name, String sex, User user) {
this.name = name;
this.sex = sex;
this.user = user;
}
我們先調用第一個構造方法創建對象然後序列化
public static void main(String[] args) {
//序列化
Student student = new Student("小明","男");
System.out.println("寫入....");
SerializeableUtils.saveObject(student,"D:\\xuliehuaTest\\");
//反序列化
Student student1 = SerializeableUtils.readObject("D:\\xuliehuaTest\\");
System.out.println("讀出....");
System.out.println(student1.toString());
}
沒問題
寫入....
讀出....
Student{name='小明', sex='男'}
調用第二個構造方法
public static void main(String[] args) {
//序列化
Student student = new Student("小明","男",new User());
System.out.println("寫入....");
SerializeableUtils.saveObject(student,"D:\\xuliehuaTest\\");
//反序列化
Student student1 = SerializeableUtils.readObject("D:\\xuliehuaTest\\");
System.out.println("讀出....");
System.out.println(student1.toString());
}
直接拋出 java.io.NotSerializableException異常,我們只需將User實現Serializable接口即可
寫入....
java.io.NotSerializableException: com.it.test.seriv.User
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.it.test.seriv.SerializeableUtils.saveObject(SerializeableUtils.java:42)
at com.it.test.seriv.Student.main(Student.java:64)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.it.test.seriv.User
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1577)
at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2287)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2211)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at com.it.test.seriv.SerializeableUtils.readObject(SerializeableUtils.java:71)
at com.it.test.seriv.Student.main(Student.java:67)
Caused by: java.io.NotSerializableException: com.it.test.seriv.User
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.it.test.seriv.SerializeableUtils.saveObject(SerializeableUtils.java:42)
at com.it.test.seriv.Student.main(Student.java:64)
讀出....
Exception in thread "main" java.lang.NullPointerException
at com.it.test.seriv.Student.main(Student.java:69)
(4)當一個類實現了Serializable接口,父類沒有實現Serializable接口,將子類序列化會怎麼樣?
- 如果父類沒有定義有參的構造方法,子類的構造方法中就不需要調用其構造函數,子類可以正常序列化。只會把子類序列化。
- 如果父類帶有有參的構造方法,子類的構造方法就需要調用父類的構造方法,這樣相當於序列化子類的時候父類也要序列化,因此父類也要實現Serializable接口。
(5)反序列化成對象之後,會調用構造函數嗎?
不會,反序列化生成對象是從二進制直接讀出來的,然後再用Object 進行強轉,不是原來的那個對象。
(6)序列化和反序列化後的對象關係?
是一個深拷貝,前後對象的引用地址不一樣