序列化基礎:
即使用ObjectOutputStream與ObjectInputStream進行對象與字節流的轉換,一般需要提供一個序列化id。
tip:默認序列化時若一個域被修飾爲transient,則不序列化該實例域。
import java.io.*;
public class Test {
public static void main(String[] args) throws Exception{
//將兩個對象序列化存儲到文件中
File f = new File("oos.txt");
System.out.println(f.exists());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));
oos.writeObject(new T(1));
oos.writeObject(new T(2));
oos.close();
//從序列化文件反序列化生成兩個對象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
T t1 = (T)ois.readObject();
T t2 = (T)ois.readObject();
t1.get();
t2.get();
ois.close();
}
}
class T implements Serializable {
private int x;
public T(int x){
this.x = x;
}
public void get(){
System.out.println(x);
}
/**
* Serilizable接口沒有抽象方法,所以以下四個方法可以不寫
*以下四個方法,爲自定義序列化時的可選方法,將由ObjectOutputStream
* 與ObjectInputStream進行反射調用。
*/
//此方法在寫入序列化文件時最先被調用,其返回一個Serilizable對象用於代替當前對象進行序列化
private Object writeReplace(){
return new T(5);
}
//此方法用於選擇保存當前對象的關鍵域(決定這個對象的實例域)到序列化文件
private void writeObject(ObjectOutputStream os) throws Exception {
//爲了往後兼容
os.defaultWriteObject();
os.writeInt(x);
}
//此方法用於從序列化文件中獲取數據用來恢復關鍵域
private void readObject(ObjectInputStream is) throws Exception{
//爲了往後兼容
is.defaultReadObject();
x = is.readInt();
}
//此方法在恢復對象時最後被調用,其返回一個對象用於替代文件恢復的對象,一般用於序列化代理
private Object readResolve(){
return new T(4);
}
}
序列化高級:
謹慎地實現Serilizable接口,其代價如下
一旦類被公佈,就降低了修改這個類的可能性
增加了bug和可能問題,可能破壞singleton模式
測試負擔增加
考慮自定義的序列化形式
考慮以下的StringList類,若使用默認的自定義形式,其將對head進行序列化,因此對鏈表的每個節點進行序列化,一來,增大了序列化的大小;二來,使得字符串列表限制只能使用鏈表Entry實現;三來,增大
了序列化時間,其將對previous與next均進行序列化,需要有昂貴的圖遍歷過程,而我們可以簡單調用next獲得字符串列表;四來,在元素多時,遞歸序列化可能造成棧溢出。
import java.io.Serializable;
/**
* Created by Doggy on 2015/9/13.
*/
public final class StringList implements Serializable{
private int size = 0;
private Entry head = null;
private static class Entry implements Serializable{
private Entry previous;
private Entry next;
private String value;
}
}
因爲對於字符串列表來說只關心字符串個數與順序,所以可以採用以下自定義的序列化方法代替,
自定義序列化時大部分實例域應該被標記爲transient(一個域被聲明爲transient,則其反序列化的值對於int爲0,引用則爲null,直到執行readObject纔會初始化)
編寫一個線程安全的可序列化類需要對readObject以及writeObject加鎖
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* Created by Doggy on 2015/9/13.
*/
public final class StringList implements Serializable{
//修飾爲transient避免默認序列化時序列化該實例域
private transient int size = 0;
private transient Entry head = null;
//添加一個增加字符串的方法
private final void addOne(String s){
Entry ent = new Entry();
ent.value = s;
Entry tmp = head;
while(tmp.next != null){
tmp = tmp.next;
}
ent.previous = tmp;
tmp.next = ent;
}
private static class Entry implements Serializable{
private Entry previous;
private Entry next;
private String value;
}
//編寫writeObject進行自定義序列化
private void writeObject(ObjectOutputStream os) throws Exception{
//爲了向後拓展,後期在類中加入一些實例域可能有用
os.defaultWriteObject();
//寫入字符串列表的大小
os.writeInt(size);
//將字符串列表中的每個字符串按順序寫入文件
while(tmp.next != null){
os.writeObject(tmp.value);
tmp = tmp.next;
}
}
private void readObject(ObjectInputStream is) throws Exception{
//爲了向後拓展,後期在類中加入一些實例域可能有用
is.defaultReadObject();
//讀取大小到對象中
size = is.readInt();
//根據列表元素以及addOne方法進行恢復
for (int i = 0; i < size; i++) {
addOne((String)is.readObject());
}
}
}
保護性地編寫readObject方法
/*
*readObject應該與構造器類似,不能(間接)調用一個可覆蓋的方法
* 且應該實現與構造器一致的有效性檢測與保護性拷貝(防止內部實例域引用泄露)
*/
private void readObject(ObjectInputStream is) throws Exception{
//爲了向後拓展,後期在類中加入一些實例域可能有用
is.defaultReadObject();
//保護性拷貝,若不實現,則可能通過僞造字節流,獲得對start與end的引用,在客戶端修改該類的start、end域,影響類的不可變性
start = new Date(start.getTime());
end = new Date(end.getTime());
//數據有效性檢測
if(start.compareTo(end) > 0){
throw new InvalidObjectException();
}
}
枚舉單例優先於使用readResolve控制的序列化單例
//可以在readResolve中直接返回單例對象,但所有實例域必須被聲明爲transient
//否則在未執行readResolve之前的readObject產生的新單例對象可能被盜用。
private Object readResolve(){
return INSTANCE;
}
考慮使用序列化代理代替序列化實例
/**
* 好處是外部類的所有實例都是從構造器創建,
* 所以可以防止以上的僞造流以及盜用者造成的危害
* 也不用特別檢測數據的有效性,因爲在構造器中已經檢測過
*/
class Period{
private final Date start;
private final Date end;
public Period(Date start,Date end){
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
//數據有效性檢測
if(start.compareTo(end) > 0){
throw new InvalidParameterException();
}
}
//使用writeReplace將序列化任務轉發給代理,所以不存在任何外部類的序列化實例
private Object writeReplace(){
return new PeriodProxy(start,end);
}
//防止對外部類使用字節流創建對象,直接對僞造流拋異常
private void readObject(){
throw new InvalidObjectException();
}
//這裏要是static,否則調用defaultWriteObject時,會將類信息寫入,導致讀異常。
private static class PeriodProxy{
private final Date start;
private final Date end;
private PeriodProxy(Date start,Date end){
this.start = start;
this.end = end;
}
//自定義實現readObject和writeObject
// ...
//實現writeResolve,將內部代理轉換爲外部類對象
private Object readResolve(){
//若爲單例則直接返回INSTANCE
return new Period(start,end);
}
}
}
序列化與反序列化
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.