java序列化與反序列化
對(duì)象序列化是一種持久化技術(shù),廣泛運(yùn)用于網(wǎng)絡(luò)傳輸、RMI等場(chǎng)景中。java對(duì)象存在于JVM運(yùn)行時(shí),然而有時(shí)期望即使JVM關(guān)閉了也可以保存當(dāng)前對(duì)象以備將來(lái)重新使用或者給其他JVM使用,為解決類似問(wèn)題可以采用持久化技術(shù)。java中對(duì)象的序列化就是將對(duì)象轉(zhuǎn)化為字符序列存放在磁盤或內(nèi)存中,反序列化則相反。對(duì)象要想序列化,要求相應(yīng)的類實(shí)現(xiàn)Serializable接口。注意,序列化保存的只是對(duì)象的狀態(tài),并不包含方法和靜態(tài)成員變量。下面是實(shí)現(xiàn)序列化的一個(gè)小例子:
public class People implements Serializable {
private static final long serialVersionUID = -2189866613532876533L;
public String mName;
public int mAge;
public static String mSchool = "WUST";
public People(String name, int age) {
mName = name;
mAge = age;
}
public static void setSchool(String mSchool) {
People.mSchool = mSchool;
}
@Override
public String toString() {
return "People{" +
"mName='" + mName + '\'' +
", mAge=" + mAge +
'}';
}
public static void main(String[] args) {
People people = new People("ldg", 22);
File file = new File("D:\\Users\\lidegui371\\Desktop\\People");
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
System.out.println(People.mSchool);
out.writeObject(people);
People.setSchool("武漢科技大學(xué)");
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
People p1 = (People) in.readObject();
System.out.println(p1);
System.out.println(People.mSchool);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
輸出為:WUST People{mName='ldg', mAge=22} 武漢科技大學(xué)
類中的serialVersionUID
用來(lái)標(biāo)識(shí)反序列的類與序列化的類是否為同一個(gè)類,假如沒有聲明serialVersionUID
,系統(tǒng)在序列化和反序列時(shí)會(huì)調(diào)用相應(yīng)的算法生成這個(gè)標(biāo)識(shí),一旦這個(gè)類有一點(diǎn)修改都會(huì)導(dǎo)致這個(gè)標(biāo)識(shí)不一樣,所以在將之前序列化對(duì)象反序列化時(shí)會(huì)因?yàn)?code>serialVersionUID不一樣而出錯(cuò)。虛擬機(jī)是否允許反序列化不僅取決于類路徑和功能代碼是否一致,還取決于這兩個(gè)類的序列化 ID 是否一致。如果serialVersionUID
一樣,即使類有改動(dòng)也可以成功地反序列化(如果類添加了字段,值為java初始值,比如int=0,String=null;如果字段減少,則不影響沒改變的其他字段)。
被transient
所修飾的字段可以在序列化時(shí)不被序列化到文件中,在反序列化時(shí)該字段被賦予初始值。如果想讓transient
修飾的字段也可以進(jìn)行序列化與反序列化可以在類中定義writeObject
和readObject
方法,自己控制序列化,如果類中沒有這兩個(gè)方法,則系統(tǒng)會(huì)調(diào)用ObjectOutputStream
中的defaultWriteObject
方法和ObjectInputStream
中的defaultReadObject
方法。
在序列化的時(shí)候,private
字段的數(shù)據(jù)是以明文的形式存放的,這在網(wǎng)絡(luò)傳輸中及其不安全,所以可以在類中定義readObject
和writeObject
方法實(shí)現(xiàn)序列化數(shù)據(jù)模糊化,比如:
private void writeObject(ObjectOutputStream out) throws IOException {
// 第一種寫法
System.out.println("未加密前的年齡:" + mAge);
ObjectOutputStream.PutField field = out.putFields();
field.put("mAge", mAge >> 1);
field.put("mName", mName);
out.writeFields();
System.out.println("加密完成");
// // 第二種寫法
// mAge = mAge >> 2;
// out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// 第一種寫法
ObjectInputStream.GetField field = in.readFields();
mName = (String) field.get("mName", "");
mAge = field.get("mAge", 0);
System.out.println("讀出來(lái)的年齡:" + mAge);
mAge = mAge << 1;
System.out.println("解密完成");
// // 第二種寫法
// in.defaultReadObject();
// mAge = mAge << 2;
}
由上可知writeObject
和readObject
方法可以自定義控制序列化。控制序列化還可以使用writeReplace
和readResolve
,只不過(guò)使用了這兩個(gè)方法后是將序列化對(duì)象或反序列化的對(duì)象給替換了。比如有一個(gè)People
類,它在writeReplace
方法中返回了一個(gè)PeopleProxy
的代理類,那么序列化的對(duì)象就變成了PeopleProxy
,序列化與反序列化時(shí)與PeopleProxy
類中相關(guān)的方法有關(guān),所以可以利用這個(gè)方法舊類序列化為新版本。readResolve
則相反,將反序列化的數(shù)據(jù)序列化為指定的對(duì)象,這個(gè)方法可以用來(lái)保護(hù)性恢復(fù)單例。這四者的調(diào)用順序?yàn)椋?code>writeReplace—>writeObject
—>readObject
—>readResolve
。需要注意的是,當(dāng)這個(gè)類調(diào)用了writeReplace
后,序列化的控制就轉(zhuǎn)到了這個(gè)方法返回的那個(gè)對(duì)象的類中了。
在JAVA中,序列化時(shí)除了實(shí)現(xiàn)Serializable
還可以實(shí)現(xiàn)Externalizable
接口,它繼承于Serializable
并聲明了兩個(gè)abstract
方法:writeExternal
和readExternal
,所以它要求手動(dòng)實(shí)現(xiàn)序列化與反序列化,用法和Serializable
的writeObject
和readObject
一樣。Externalizable
在效率上比Serializable
要高,但如果不需要自定義序列化過(guò)程,一般使用Serializable
。
在android開發(fā)中,除了使用Serializable
還可以使用Parcelable
實(shí)現(xiàn)對(duì)象序列化。Serializable
使用起來(lái)開銷較大,通過(guò)IO流寫入到磁盤和傳輸?shù)骄W(wǎng)絡(luò),android設(shè)計(jì)的Parcelable是為了在程序內(nèi)不同組件間和android跨進(jìn)程而設(shè)計(jì)的一種序列化方式,它是通過(guò)IBinder通信的消息的載體,攜帶的數(shù)據(jù)僅存在內(nèi)存中,由于內(nèi)存的讀寫速度比磁盤的要快,所以在內(nèi)存間的數(shù)據(jù)傳輸推薦使用Parcelable
,但因其在持久化和網(wǎng)絡(luò)傳輸上操作復(fù)雜,在這方面推薦使用Serializable
。